From 7da3efb962acf99f52bb8363ef071e75576c6675 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 2 Dec 2023 17:18:32 +0900 Subject: [PATCH 01/76] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=20v3.9.1-dev=20=E9=96=8B=E7=99=BA=E9=96=8B=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 2 +- OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 5 +++-- appveyor.yml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ffc2bc540..41d29e9b1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,6 @@ 更新履歴 -==== Ver 3.9.0(2023/12/03) +==== Unreleased * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 * CHG: タイムライン更新時に全件ではなく新着投稿のみ差分を取得する動作に変更 * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不具合を修正 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index 3c356e1bf..b81f8665a 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.9.0.0")] +[assembly: AssemblyVersion("3.9.0.1")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index 0201229ab..b90eb7404 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -580,7 +580,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// - ///==== Ver 3.9.0(2023/12/03) + ///==== Unreleased /// * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 /// * CHG: タイムライン更新時に全件ではなく新着投稿のみ差分を取得する動作に変更 /// * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不具合を修正 @@ -592,7 +592,8 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// * NEW: graphqlエンドポイントを使用したプロフィール情報の取得に対応 /// * NEW: graphqlエンドポイントを使用したユーザータイムラインの取得に対応 /// * CHG: タイムライン更新が停止する不具合が報告される件への暫定的な対処 - /// - タイムライン更新に30秒以上掛かっている場合は完了を待機せず次のタイマーを [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// - タイムライン更新に30秒以上掛かっている場合は完了を待機せず次のタイマーを開始させる + /// - [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { diff --git a/appveyor.yml b/appveyor.yml index 6d75fa444..fe7dd11de 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.8.0.{build} +version: 3.9.0.{build} os: Visual Studio 2022 From 8292f41291ed862b33b66dc7671152c669b1c6a7 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 2 Dec 2023 17:20:07 +0900 Subject: [PATCH 02/76] =?UTF-8?q?ChangeLog=E3=81=AE=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E3=83=9F=E3=82=B9=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: 7da3efb9 ("バージョン v3.9.1-dev 開発開始") --- CHANGELOG.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 41d29e9b1..1b712a082 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,8 @@ 更新履歴 ==== Unreleased + +==== Ver 3.9.0(2023/12/03) * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 * CHG: タイムライン更新時に全件ではなく新着投稿のみ差分を取得する動作に変更 * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不具合を修正 From d0aaa9ada60678299a5ebd0adb181e556e71d304 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 2 Dec 2023 20:58:35 +0900 Subject: [PATCH 03/76] =?UTF-8?q?HttpClientBuilder=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/HttpClientBuilderTest.cs | 104 ++++++++++++++++++ OpenTween.Tests/OpenTween.Tests.csproj | 1 + OpenTween/Api/ImgurApi.cs | 6 +- OpenTween/Api/MobypictureApi.cs | 8 +- OpenTween/Api/TwitterApi.cs | 5 +- OpenTween/Connection/HttpClientBuilder.cs | 76 +++++++++++++ OpenTween/Connection/Networking.cs | 65 +++++------ OpenTween/Connection/TwitterApiConnection.cs | 42 ++++--- OpenTween/ShortUrl.cs | 9 +- 9 files changed, 249 insertions(+), 67 deletions(-) create mode 100644 OpenTween.Tests/Connection/HttpClientBuilderTest.cs create mode 100644 OpenTween/Connection/HttpClientBuilder.cs diff --git a/OpenTween.Tests/Connection/HttpClientBuilderTest.cs b/OpenTween.Tests/Connection/HttpClientBuilderTest.cs new file mode 100644 index 000000000..201784b25 --- /dev/null +++ b/OpenTween.Tests/Connection/HttpClientBuilderTest.cs @@ -0,0 +1,104 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Net.Http; +using Moq; +using Xunit; + +namespace OpenTween.Connection +{ + public class HttpClientBuilderTest + { + [Fact] + public void Build_Test() + { + var builder = new HttpClientBuilder(); + using var client = builder.Build(); + } + + [Fact] + public void SetupHttpClientHandler_Test() + { + var builder = new HttpClientBuilder(); + builder.SetupHttpClientHandler(x => x.AllowAutoRedirect = true); + builder.AddHandler(x => + { + var httpClientHandler = (HttpClientHandler)x; + Assert.True(httpClientHandler.AllowAutoRedirect); + return x; + }); + using var client = builder.Build(); + } + + [Fact] + public void AddHandler_Test() + { + var count = 0; + + var builder = new HttpClientBuilder(); + builder.AddHandler(x => + { + count++; + Assert.IsType(x); + return x; + }); + using var client = builder.Build(); + + Assert.Equal(1, count); + } + + [Fact] + public void AddHandler_NestingTest() + { + var count = 0; + HttpMessageHandler? handler = null; + + var builder = new HttpClientBuilder(); + builder.AddHandler(x => + { + count++; + handler = Mock.Of(); + return handler; + }); + builder.AddHandler(x => + { + count++; + Assert.NotNull(x); + Assert.Same(handler, x); + return x; + }); + using var client = builder.Build(); + + Assert.Equal(2, count); + } + + [Fact] + public void SetupHttpClient_Test() + { + var builder = new HttpClientBuilder(); + builder.SetupHttpClient(x => x.Timeout = TimeSpan.FromSeconds(10)); + using var client = builder.Build(); + + Assert.Equal(TimeSpan.FromSeconds(10), client.Timeout); + } + } +} diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index f9444e3b6..aed21e9d0 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/OpenTween/Api/ImgurApi.cs b/OpenTween/Api/ImgurApi.cs index 591bec315..60422bc5c 100644 --- a/OpenTween/Api/ImgurApi.cs +++ b/OpenTween/Api/ImgurApi.cs @@ -57,8 +57,10 @@ public ImgurApi(ApiKey clientId, HttpClient? http) } else { - this.http = Networking.CreateHttpClient(Networking.CreateHttpClientHandler()); - this.http.Timeout = Networking.UploadImageTimeout; + var builder = Networking.CreateHttpClientBuilder(); + builder.SetupHttpClient(x => x.Timeout = Networking.UploadImageTimeout); + + this.http = builder.Build(); } } diff --git a/OpenTween/Api/MobypictureApi.cs b/OpenTween/Api/MobypictureApi.cs index d76d4347c..02f36d427 100644 --- a/OpenTween/Api/MobypictureApi.cs +++ b/OpenTween/Api/MobypictureApi.cs @@ -53,9 +53,11 @@ public MobypictureApi(ApiKey apiKey, TwitterApi twitterApi) { this.apiKey = apiKey; - var handler = twitterApi.CreateOAuthEchoHandler(AuthServiceProvider, OAuthRealm); - this.http = Networking.CreateHttpClient(handler); - this.http.Timeout = Networking.UploadImageTimeout; + var builder = Networking.CreateHttpClientBuilder(); + builder.SetupHttpClient(x => x.Timeout = Networking.UploadImageTimeout); + builder.AddHandler(x => twitterApi.CreateOAuthEchoHandler(x, AuthServiceProvider, OAuthRealm)); + + this.http = builder.Build(); } public MobypictureApi(ApiKey apiKey, HttpClient http) diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 157cc2d73..1f3b68e63 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -25,6 +25,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -815,8 +816,8 @@ public Task MediaMetadataCreate(long mediaId, string altText) return this.Connection.PostJsonAsync(endpoint, json); } - public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri? realm = null) - => ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(authServiceProvider, realm); + public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null) + => ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(innerHandler, authServiceProvider, realm); public void Dispose() => this.ApiConnection?.Dispose(); diff --git a/OpenTween/Connection/HttpClientBuilder.cs b/OpenTween/Connection/HttpClientBuilder.cs new file mode 100644 index 000000000..bb5c1cea9 --- /dev/null +++ b/OpenTween/Connection/HttpClientBuilder.cs @@ -0,0 +1,76 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class HttpClientBuilder + { + private readonly List> setupHttpClientHandler = new(); + private readonly List> customHandlers = new(); + private readonly List> setupHttpClient = new(); + + public void SetupHttpClientHandler(Action func) + => this.setupHttpClientHandler.Add(func); + + public void AddHandler(Func func) + => this.customHandlers.Add(func); + + public void SetupHttpClient(Action func) + => this.setupHttpClient.Add(func); + + public HttpClient Build() + { + WebRequestHandler? handler = null; + HttpMessageHandler? wrappedHandler = null; + HttpClient? client = null; + try + { + handler = new(); + foreach (var setupFunc in this.setupHttpClientHandler) + setupFunc(handler); + + wrappedHandler = handler; + foreach (var handlerFunc in this.customHandlers) + wrappedHandler = handlerFunc(wrappedHandler); + + client = new(wrappedHandler, disposeHandler: true); + + foreach (var setupFunc in this.setupHttpClient) + setupFunc(client); + + return client; + } + catch + { + client?.Dispose(); + wrappedHandler?.Dispose(); + handler?.Dispose(); + throw; + } + } + } +} diff --git a/OpenTween/Connection/Networking.cs b/OpenTween/Connection/Networking.cs index bdc0e47ad..b7511d0d9 100644 --- a/OpenTween/Connection/Networking.cs +++ b/OpenTween/Connection/Networking.cs @@ -87,7 +87,7 @@ static Networking() { DefaultTimeout = TimeSpan.FromSeconds(20); UploadImageTimeout = TimeSpan.FromSeconds(60); - globalHttpClient = CreateHttpClient(new HttpClientHandler()); + globalHttpClient = CreateHttpClientBuilder().Build(); } /// @@ -134,50 +134,39 @@ public static void SetWebProxy( } /// - /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します - /// - [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] - public static WebRequestHandler CreateHttpClientHandler() - { - var handler = new WebRequestHandler - { - UseCookies = false, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds, - }; - - if (Networking.Proxy != null) - { - handler.UseProxy = true; - handler.Proxy = Networking.Proxy; - } - else - { - handler.UseProxy = false; - } - - return handler; - } - - /// - /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します + /// OpenTween のユーザー設定を適用した を返します /// /// /// 通常は Networking.Http を使用すべきです。 /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。 /// - [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] - public static HttpClient CreateHttpClient(HttpMessageHandler handler) + public static HttpClientBuilder CreateHttpClientBuilder() { - HttpClient client; - if (ForceIPv4) - client = new HttpClient(new ForceIPv4Handler(handler)); - else - client = new HttpClient(handler); + var builder = new HttpClientBuilder(); + + builder.SetupHttpClientHandler(x => + { + x.UseCookies = false; + x.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + x.ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds; + + if (Networking.Proxy != null) + { + x.UseProxy = true; + x.Proxy = Networking.Proxy; + } + else + { + x.UseProxy = false; + } + }); + + builder.SetupHttpClient(x => x.Timeout = Networking.DefaultTimeout); - client.Timeout = Networking.DefaultTimeout; + if (forceIPv4) + builder.AddHandler(x => new ForceIPv4Handler(x)); - return client; + return builder; } public static string GetUserAgentString(bool fakeMSIE = false) @@ -200,7 +189,7 @@ internal static void CheckInitialized() [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] private static void OnWebProxyChanged(EventArgs e) { - var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler()); + var newClient = Networking.CreateHttpClientBuilder().Build(); var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient); oldClient.Dispose(); diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 16e2c342b..b8bd085e1 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -488,12 +488,12 @@ protected static async Task CheckStatusCode(HttpResponseMessage response) } } - public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri? realm = null) + public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null) { var uri = new Uri(RestApiBase, authServiceProvider); return OAuthEchoHandler.CreateHandler( - Networking.CreateHttpClientHandler(), + innerHandler, uri, this.appToken.OAuth1ConsumerKey, this.appToken.OAuth1ConsumerSecret, @@ -611,35 +611,43 @@ await TwitterApiConnection.CheckStatusCode(response) private static HttpClient InitializeHttpClient(ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret, bool disableGzip = false) { - var innerHandler = Networking.CreateHttpClientHandler(); - innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + var builder = Networking.CreateHttpClientBuilder(); - if (disableGzip) - innerHandler.AutomaticDecompression = DecompressionMethods.None; + builder.SetupHttpClientHandler(x => + { + x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + + if (disableGzip) + x.AutomaticDecompression = DecompressionMethods.None; + }); - var handler = new OAuthHandler(innerHandler, consumerKey, consumerSecret, accessToken, accessSecret); + builder.AddHandler(x => new OAuthHandler(x, consumerKey, consumerSecret, accessToken, accessSecret)); - return Networking.CreateHttpClient(handler); + return builder.Build(); } private static HttpClient InitializeHttpClient(TwitterAppToken appToken, string accessToken, string accessSecret, bool disableGzip = false) { - var innerHandler = Networking.CreateHttpClientHandler(); - innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + var builder = Networking.CreateHttpClientBuilder(); - if (disableGzip) - innerHandler.AutomaticDecompression = DecompressionMethods.None; + builder.SetupHttpClientHandler(x => + { + x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); - HttpMessageHandler handler = appToken.AuthType switch + if (disableGzip) + x.AutomaticDecompression = DecompressionMethods.None; + }); + + builder.AddHandler(x => appToken.AuthType switch { APIAuthType.OAuth1 - => new OAuthHandler(innerHandler, appToken.OAuth1ConsumerKey, appToken.OAuth1ConsumerSecret, accessToken, accessSecret), + => new OAuthHandler(x, appToken.OAuth1ConsumerKey, appToken.OAuth1ConsumerSecret, accessToken, accessSecret), APIAuthType.TwitterComCookie - => new TwitterComCookieHandler(innerHandler, appToken.TwitterComCookie), + => new TwitterComCookieHandler(x, appToken.TwitterComCookie), _ => throw new NotImplementedException(), - }; + }); - return Networking.CreateHttpClient(handler); + return builder.Build(); } } } diff --git a/OpenTween/ShortUrl.cs b/OpenTween/ShortUrl.cs index b6dab0e06..e417022b6 100644 --- a/OpenTween/ShortUrl.cs +++ b/OpenTween/ShortUrl.cs @@ -515,13 +515,12 @@ private Uri UpgradeToHttpsIfAvailable(Uri original) [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] private static HttpClient CreateDefaultHttpClient() { - var handler = Networking.CreateHttpClientHandler(); - handler.AllowAutoRedirect = false; + var builder = Networking.CreateHttpClientBuilder(); - var http = Networking.CreateHttpClient(handler); - http.Timeout = TimeSpan.FromSeconds(30); + builder.SetupHttpClientHandler(x => x.AllowAutoRedirect = false); + builder.SetupHttpClient(x => x.Timeout = TimeSpan.FromSeconds(30)); - return http; + return builder.Build(); } } } From 065be2ce6a9fda9293dd47e17d3ea104e674e079 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 15:19:53 +0900 Subject: [PATCH 04/76] =?UTF-8?q?AppVeyor=E3=81=A7=E3=81=AE=E3=83=93?= =?UTF-8?q?=E3=83=AB=E3=83=89=E6=99=82=E3=81=ABCodecov=E3=81=B8=E3=81=AE?= =?UTF-8?q?=E3=82=AB=E3=83=90=E3=83=AC=E3=83=83=E3=82=B8=E3=81=AE=E9=80=81?= =?UTF-8?q?=E4=BF=A1=E3=82=92=E8=A1=8C=E3=82=8F=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions で送信しているデータと重複しているため --- appveyor.yml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index fe7dd11de..ad1b7b125 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,21 +58,12 @@ init: before_build: - nuget restore -test_script: - - cmd: | - set altCoverVersion=8.6.61 - set xunitVersion=2.4.2 - set targetFramework=net48 - set nugetPackages=%UserProfile%\.nuget\packages - - %nugetPackages%\altcover\%altCoverVersion%\tools\net472\AltCover.exe --inputDirectory .\OpenTween.Tests\bin\%CONFIGURATION%\%targetFramework%\ --outputDirectory .\__Instrumented\ --assemblyFilter "?^OpenTween(?!\.Tests)" --typeFilter "?^OpenTween\." --fileFilter "\.Designer\.cs" --visibleBranches - - %nugetPackages%\altcover\%altCoverVersion%\tools\net472\AltCover.exe runner --recorderDirectory .\__Instrumented\ --executable %nugetPackages%\xunit.runner.console\%xunitVersion%\tools\net472\xunit.console.exe -- .\__Instrumented\OpenTween.Tests.dll +test: + assemblies: + only: + - OpenTween.Tests.dll after_test: - - ps: | - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - .\codecov.exe -f coverage.xml - ps: | $env:PATH = $env:PATH + ';C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\Roslyn\;C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\' $binDir = '.\OpenTween\bin\' + $env:CONFIGURATION + '\net48\' From 3fd339d6dd8cfacaf28890d508da64aaa82d066c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 16:39:36 +0900 Subject: [PATCH 05/76] =?UTF-8?q?AppVeyor=E7=94=A8=E3=81=AEmsbuild.rsp?= =?UTF-8?q?=E3=82=92before=5Fbuild=E3=81=A7=E9=83=BD=E5=BA=A6=E7=94=9F?= =?UTF-8?q?=E6=88=90=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit msbuild.rsp は GitHub Actions でのビルド時にも適用されてしまうためリポジトリに直接含めない --- appveyor.yml | 1 + msbuild.rsp | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 msbuild.rsp diff --git a/appveyor.yml b/appveyor.yml index ad1b7b125..1893d8839 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -57,6 +57,7 @@ init: before_build: - nuget restore + - ps: Set-Content .\msbuild.rsp "/warnaserror /p:DebugType=None" test: assemblies: diff --git a/msbuild.rsp b/msbuild.rsp deleted file mode 100644 index 7f712b3d1..000000000 --- a/msbuild.rsp +++ /dev/null @@ -1,3 +0,0 @@ -# MSBuild response file for AppVeyor build - -/warnaserror /p:DebugType=None From d3d7572e1d1e36f1ad2e0f353613b2aeeefa0cf9 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 15:23:31 +0900 Subject: [PATCH 06/76] =?UTF-8?q?GitHub=20Actions=E3=81=A7=E3=81=AEPull=20?= =?UTF-8?q?Request=E3=81=AE=E3=83=93=E3=83=AB=E3=83=89=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=83=96=E3=83=A9=E3=83=B3=E3=83=81=E3=81=AEHEAD=E3=82=92?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit デフォルトでは自動生成された develop ブランチとのマージコミットをチェックアウトしている --- .github/workflows/build.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ace1bd33..b540e7f34 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 @@ -55,6 +57,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 @@ -169,8 +173,7 @@ jobs: $env:PATH = $env:PATH + ';C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\Roslyn\' $binDir = '.\OpenTween\bin\' + $env:CONFIGURATION + '\net48\' $destPath = 'OpenTween.zip' - $headCommit = '${{ github.event.pull_request.head.sha }}' - .\tools\build-zip-archive.ps1 -BinDir $binDir -DestPath $destPath -HeadCommit $headCommit + .\tools\build-zip-archive.ps1 -BinDir $binDir -DestPath $destPath - name: Upload build result uses: actions/upload-artifact@v3 From 1967640a01463bdbb28afb0706b60b28bc85d2b6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 15:51:23 +0900 Subject: [PATCH 07/76] =?UTF-8?q?GitHub=20Actions=E3=81=A7=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E5=AE=9F=E8=A1=8C=E7=94=A8=E3=81=AE=E3=83=93?= =?UTF-8?q?=E3=83=AB=E3=83=89=E3=81=AB=E3=81=AFDebugType=3DNone=E3=82=92?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DebugType=None を指定すると AltCover によるコードカバレッジの計測ができなくなるため --- .github/workflows/build.yml | 143 ++-------------------------------- .github/workflows/package.yml | 57 ++++++++++++++ .github/workflows/test.yml | 103 ++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 137 deletions(-) create mode 100644 .github/workflows/package.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b540e7f34..331746e00 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,10 @@ -name: Build +name: Run msbuild on: - push: - branches: ['develop', 'release'] - pull_request: + workflow_call: + inputs: + msbuild_args: + type: string env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages @@ -39,7 +40,7 @@ jobs: - name: Build shell: pwsh run: | - msbuild /target:restore,build "/p:Configuration=$($env:CONFIGURATION)" /p:DebugType=None /verbosity:minimal + msbuild /target:restore,build "/p:Configuration=$($env:CONFIGURATION)" /verbosity:minimal ${{ inputs.msbuild_args }} - name: Upload build result uses: actions/upload-artifact@v3 @@ -47,137 +48,5 @@ jobs: name: build path: | ./OpenTween/bin/ - ./OpenTween/obj/ ./OpenTween.Tests/bin/ retention-days: 1 - - test: - runs-on: windows-2022 - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.1 - - - name: Set configuration env - shell: pwsh - run: | - if ($env:GITHUB_REF -eq 'refs/heads/release') { - echo 'CONFIGURATION=Release' >> $env:GITHUB_ENV - } else { - echo 'CONFIGURATION=Debug' >> $env:GITHUB_ENV - } - - - uses: actions/cache@v3 - with: - path: ${{ github.workspace }}/.nuget/packages - key: nuget-${{ hashFiles('*/*.csproj') }} - restore-keys: | - nuget- - - - name: Restore build result - uses: actions/download-artifact@v3 - with: - name: build - - - name: Run tests - shell: pwsh - run: | - $altCoverVersion = '8.6.61' - $xunitVersion = '2.4.2' - $targetFramework = 'net48' - $altCoverPath = "$($env:NUGET_PACKAGES)\altcover\$($altCoverVersion)\tools\net472\AltCover.exe" - $xunitPath = "$($env:NUGET_PACKAGES)\xunit.runner.console\$($xunitVersion)\tools\net472\xunit.console.exe" - - $p = Start-Process ` - -FilePath $altCoverPath ` - -ArgumentList ( - '--inputDirectory', - ".\OpenTween.Tests\bin\$($env:CONFIGURATION)\$($targetFramework)", - '--outputDirectory', - '.\__Instrumented\', - '--assemblyFilter', - '?^OpenTween(?!\.Tests)', - '--typeFilter', - '?^OpenTween\.', - '--fileFilter', - '\.Designer\.cs', - '--visibleBranches' - ) ` - -NoNewWindow ` - -PassThru ` - -Wait - - if ($p.ExitCode -ne 0) { - exit $p.ExitCode - } - - $p = Start-Process ` - -FilePath $altCoverPath ` - -ArgumentList ( - 'runner', - '--recorderDirectory', - '.\__Instrumented\', - '--executable', - $xunitPath, - '--', - '.\__Instrumented\OpenTween.Tests.dll' - ) ` - -NoNewWindow ` - -PassThru ` - -Wait - - if ($p.ExitCode -ne 0) { - exit $p.ExitCode - } - - - name: Upload test results to codecov - shell: pwsh - run: | - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - .\codecov.exe -f coverage.xml - - package: - runs-on: windows-2022 - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: '${{ github.event.pull_request.head.sha }}' - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.1 - - - name: Set configuration env - shell: pwsh - run: | - if ($env:GITHUB_REF -eq 'refs/heads/release') { - echo 'CONFIGURATION=Release' >> $env:GITHUB_ENV - } else { - echo 'CONFIGURATION=Debug' >> $env:GITHUB_ENV - } - - - name: Restore build result - uses: actions/download-artifact@v3 - with: - name: build - - - name: Build package - shell: powershell # runtime-versionを取得するため従来のPowershellを使用する - run: | - $env:PATH = $env:PATH + ';C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\Roslyn\' - $binDir = '.\OpenTween\bin\' + $env:CONFIGURATION + '\net48\' - $destPath = 'OpenTween.zip' - .\tools\build-zip-archive.ps1 -BinDir $binDir -DestPath $destPath - - - name: Upload build result - uses: actions/upload-artifact@v3 - with: - name: package - path: | - ./OpenTween.zip diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 000000000..aeda25940 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,57 @@ +name: Build reproducible zip archive + +on: + push: + branches: ['develop', 'release'] + pull_request: + +env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + +jobs: + build: + uses: ./.github/workflows/build.yml + with: + # package のビルド時は *.pdb を生成しない (https://github.com/opentween/OpenTween/pull/256) + msbuild_args: /p:DebugType=None + + package: + runs-on: windows-2022 + needs: [build] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: '${{ github.event.pull_request.head.sha }}' + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + + - name: Set configuration env + shell: pwsh + run: | + if ($env:GITHUB_REF -eq 'refs/heads/release') { + echo 'CONFIGURATION=Release' >> $env:GITHUB_ENV + } else { + echo 'CONFIGURATION=Debug' >> $env:GITHUB_ENV + } + + - name: Restore build result + uses: actions/download-artifact@v3 + with: + name: build + + - name: Build package + shell: powershell # runtime-versionを取得するため従来のPowershellを使用する + run: | + $env:PATH = $env:PATH + ';C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\Roslyn\' + $binDir = '.\OpenTween\bin\' + $env:CONFIGURATION + '\net48\' + $destPath = 'OpenTween.zip' + .\tools\build-zip-archive.ps1 -BinDir $binDir -DestPath $destPath + + - name: Upload build result + uses: actions/upload-artifact@v3 + with: + name: package + path: | + ./OpenTween.zip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..3829b7fe6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,103 @@ +name: Run tests + +on: + push: + branches: ['develop', 'release'] + pull_request: + +env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + +jobs: + build: + uses: ./.github/workflows/build.yml + + test: + runs-on: windows-2022 + needs: [build] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + + - name: Set configuration env + shell: pwsh + run: | + if ($env:GITHUB_REF -eq 'refs/heads/release') { + echo 'CONFIGURATION=Release' >> $env:GITHUB_ENV + } else { + echo 'CONFIGURATION=Debug' >> $env:GITHUB_ENV + } + + - uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.nuget/packages + key: nuget-${{ hashFiles('*/*.csproj') }} + restore-keys: | + nuget- + + - name: Restore build result + uses: actions/download-artifact@v3 + with: + name: build + + - name: Run tests + shell: pwsh + run: | + $altCoverVersion = '8.6.61' + $xunitVersion = '2.4.2' + $targetFramework = 'net48' + $altCoverPath = "$($env:NUGET_PACKAGES)\altcover\$($altCoverVersion)\tools\net472\AltCover.exe" + $xunitPath = "$($env:NUGET_PACKAGES)\xunit.runner.console\$($xunitVersion)\tools\net472\xunit.console.exe" + + $p = Start-Process ` + -FilePath $altCoverPath ` + -ArgumentList ( + '--inputDirectory', + ".\OpenTween.Tests\bin\$($env:CONFIGURATION)\$($targetFramework)", + '--outputDirectory', + '.\__Instrumented\', + '--assemblyFilter', + '?^OpenTween(?!\.Tests)', + '--typeFilter', + '?^OpenTween\.', + '--fileFilter', + '\.Designer\.cs', + '--visibleBranches' + ) ` + -NoNewWindow ` + -PassThru ` + -Wait + + if ($p.ExitCode -ne 0) { + exit $p.ExitCode + } + + $p = Start-Process ` + -FilePath $altCoverPath ` + -ArgumentList ( + 'runner', + '--recorderDirectory', + '.\__Instrumented\', + '--executable', + $xunitPath, + '--', + '.\__Instrumented\OpenTween.Tests.dll' + ) ` + -NoNewWindow ` + -PassThru ` + -Wait + + if ($p.ExitCode -ne 0) { + exit $p.ExitCode + } + + - name: Upload test results to codecov + shell: pwsh + run: | + Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe + .\codecov.exe -f coverage.xml From 70d4ba67455fc79ea1333a61a447eee5da2962d6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 23:39:43 +0900 Subject: [PATCH 08/76] =?UTF-8?q?attach-svn-logs.sh=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + tools/attach-svn-logs.sh | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100755 tools/attach-svn-logs.sh diff --git a/.gitattributes b/.gitattributes index 176a458f9..2e46fbac0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto +*.sh eol=lf diff --git a/tools/attach-svn-logs.sh b/tools/attach-svn-logs.sh new file mode 100755 index 000000000..48bee95f3 --- /dev/null +++ b/tools/attach-svn-logs.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Subversion で管理されていた頃の Tween のコミットログ (SourceForge.JP, CodeRepos) を接続するスクリプト +# +# 実行後は `git blame Tween_svn-last -- Tween/Tween.vb` のように変更履歴を追うことができます + +set -eu + +git remote add tween_coderepos https://github.com/opentween/Tween_CodeRepos.git + +# Tween_CodeRepos を取得 + Tween_v1.1.0.0 などのタグを強制的に上書き +git fetch --force --tags tween_coderepos + +# OpenTween のコミットログのうち以下の 2 つを git replace で置換する + +# r1643: 3ポスト以上の通知はまとめる +git replace 6a654b6edaa338fc890494c9fa6a19594277b6b2 $(git rev-parse Tween_sourceforge-last^0) + +# r1521: 1010リリース +git replace ddbe79b3cfb2baa4e4799a00a2004ba10546aef1 $(git rev-parse Tween_v1.0.1.0^0) From 4803a637de164a7a249a96a3aa4ba4e6e9a48b7d Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 00:05:48 +0900 Subject: [PATCH 09/76] =?UTF-8?q?SettingTabs=E3=81=8B=E3=82=89=E8=AA=AD?= =?UTF-8?q?=E3=81=BF=E8=BE=BC=E3=82=93=E3=81=A0=E3=82=BF=E3=83=96=E5=90=8D?= =?UTF-8?q?=E3=81=8C=E9=87=8D=E8=A4=87=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AE=E5=91=BD=E5=90=8D=E8=A6=8F=E5=89=87=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Models/TabInformationTest.cs | 55 ++++++++++++++++++++ OpenTween/Models/TabInformations.cs | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/OpenTween.Tests/Models/TabInformationTest.cs b/OpenTween.Tests/Models/TabInformationTest.cs index f4ba637d9..25de25bde 100644 --- a/OpenTween.Tests/Models/TabInformationTest.cs +++ b/OpenTween.Tests/Models/TabInformationTest.cs @@ -310,6 +310,61 @@ public void SelectTab_Test() public void SelectTab_NotExistTest() => Assert.Throws(() => this.tabinfo.SelectTab("INVALID")); + [Fact] + public void LoadTabsFromSettings_Test() + { + var settingTabs = new SettingTabs + { + Tabs = + { + new() + { + TabName = "hoge", + TabType = MyCommon.TabUsageType.PublicSearch, + SearchWords = "aaa", + }, + }, + }; + var tabinfo = this.CreateInstance(); + tabinfo.LoadTabsFromSettings(settingTabs); + Assert.Equal(1, tabinfo.Tabs.Count); + + var tab = (PublicSearchTabModel)tabinfo.Tabs["hoge"]; + Assert.Equal("aaa", tab.SearchWords); + } + + [Fact] + public void LoadTabsFromSettings_DuplicateTabNameTest() + { + var settingTabs = new SettingTabs + { + Tabs = + { + new() + { + TabName = "hoge", + TabType = MyCommon.TabUsageType.PublicSearch, + SearchWords = "aaa", + }, + new() + { + TabName = "hoge", // 重複したタブ名 + TabType = MyCommon.TabUsageType.PublicSearch, + SearchWords = "bbb", + }, + }, + }; + var tabinfo = this.CreateInstance(); + tabinfo.LoadTabsFromSettings(settingTabs); + Assert.Equal(2, tabinfo.Tabs.Count); + + var tab1 = (PublicSearchTabModel)tabinfo.Tabs["hoge"]; + Assert.Equal("aaa", tab1.SearchWords); + + var tab2 = (PublicSearchTabModel)tabinfo.Tabs["hoge2"]; + Assert.Equal("bbb", tab2.SearchWords); + } + [Theory] [InlineData(MyCommon.TabUsageType.Home, typeof(HomeTabModel))] [InlineData(MyCommon.TabUsageType.Mentions, typeof(MentionsTabModel))] diff --git a/OpenTween/Models/TabInformations.cs b/OpenTween/Models/TabInformations.cs index 7b338462a..d3c2d61b3 100644 --- a/OpenTween/Models/TabInformations.cs +++ b/OpenTween/Models/TabInformations.cs @@ -230,7 +230,7 @@ public void LoadTabsFromSettings(SettingTabs settingTabs) continue; if (this.ContainsTab(tab.TabName)) - tab.TabName = this.MakeTabName("MyTab"); + tab.TabName = this.MakeTabName(tab.TabName); this.AddTab(tab); } From fe963d77ab7e56a6f2a59a59843323973f77c9ed Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 00:07:38 +0900 Subject: [PATCH 10/76] =?UTF-8?q?Form=E3=81=8A=E3=82=88=E3=81=B3Control?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=E3=82=AF=E3=83=A9=E3=82=B9=E7=AD=89=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=82=B9=E3=83=88=E3=83=A9=E3=82=AF=E3=82=BF?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E3=81=99=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/ApiInfoDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/AppendSettingDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/ApplicationContainerTest.cs | 41 +++++++++++++++++ OpenTween.Tests/AtIdSupplementTest.cs | 39 ++++++++++++++++ OpenTween.Tests/AuthDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/AuthTypeSelectDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/BingTest.cs | 4 ++ OpenTween.Tests/DetailsListViewTest.cs | 40 ++++++++++++++++ OpenTween.Tests/EncryptApiKeyDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/ErrorReportHandlerTest.cs | 39 ++++++++++++++++ OpenTween.Tests/FilterDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/HookGlobalHotkeyTest.cs | 41 +++++++++++++++++ OpenTween.Tests/IconAssetsManagerTest.cs | 39 ++++++++++++++++ OpenTween.Tests/ImageCacheTest.cs | 39 ++++++++++++++++ OpenTween.Tests/InputTabNameTest.cs | 39 ++++++++++++++++ .../InternetSecurityManagerTest.cs | 41 +++++++++++++++++ OpenTween.Tests/ListAvailableTest.cs | 39 ++++++++++++++++ OpenTween.Tests/ListManageTest.cs | 42 +++++++++++++++++ OpenTween.Tests/LoginDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/MemoryImageListTest.cs | 39 ++++++++++++++++ .../MouseWheelMessageFilterTest.cs | 6 +++ OpenTween.Tests/MyListsTest.cs | 39 ++++++++++++++++ OpenTween.Tests/OpenURLTest.cs | 39 ++++++++++++++++ OpenTween.Tests/SearchWordDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/SendErrorReportFormTest.cs | 39 ++++++++++++++++ OpenTween.Tests/TimelineListViewDrawerTest.cs | 46 +++++++++++++++++++ OpenTween.Tests/TimelineListViewStateTest.cs | 43 +++++++++++++++++ OpenTween.Tests/ToolStripLabelHistoryTest.cs | 40 ++++++++++++++++ OpenTween.Tests/TweenAboutBoxTest.cs | 39 ++++++++++++++++ OpenTween.Tests/TweetDetailsViewTest.cs | 6 +++ OpenTween.Tests/TweetThumbnailControlTest.cs | 39 ++++++++++++++++ OpenTween.Tests/UpdateDialogTest.cs | 39 ++++++++++++++++ OpenTween.Tests/WaitingDialogTest.cs | 39 ++++++++++++++++ 33 files changed, 1208 insertions(+) create mode 100644 OpenTween.Tests/ApiInfoDialogTest.cs create mode 100644 OpenTween.Tests/AppendSettingDialogTest.cs create mode 100644 OpenTween.Tests/ApplicationContainerTest.cs create mode 100644 OpenTween.Tests/AtIdSupplementTest.cs create mode 100644 OpenTween.Tests/AuthDialogTest.cs create mode 100644 OpenTween.Tests/AuthTypeSelectDialogTest.cs create mode 100644 OpenTween.Tests/DetailsListViewTest.cs create mode 100644 OpenTween.Tests/EncryptApiKeyDialogTest.cs create mode 100644 OpenTween.Tests/ErrorReportHandlerTest.cs create mode 100644 OpenTween.Tests/FilterDialogTest.cs create mode 100644 OpenTween.Tests/HookGlobalHotkeyTest.cs create mode 100644 OpenTween.Tests/IconAssetsManagerTest.cs create mode 100644 OpenTween.Tests/ImageCacheTest.cs create mode 100644 OpenTween.Tests/InputTabNameTest.cs create mode 100644 OpenTween.Tests/InternetSecurityManagerTest.cs create mode 100644 OpenTween.Tests/ListAvailableTest.cs create mode 100644 OpenTween.Tests/ListManageTest.cs create mode 100644 OpenTween.Tests/LoginDialogTest.cs create mode 100644 OpenTween.Tests/MemoryImageListTest.cs create mode 100644 OpenTween.Tests/MyListsTest.cs create mode 100644 OpenTween.Tests/OpenURLTest.cs create mode 100644 OpenTween.Tests/SearchWordDialogTest.cs create mode 100644 OpenTween.Tests/SendErrorReportFormTest.cs create mode 100644 OpenTween.Tests/TimelineListViewDrawerTest.cs create mode 100644 OpenTween.Tests/TimelineListViewStateTest.cs create mode 100644 OpenTween.Tests/ToolStripLabelHistoryTest.cs create mode 100644 OpenTween.Tests/TweenAboutBoxTest.cs create mode 100644 OpenTween.Tests/TweetThumbnailControlTest.cs create mode 100644 OpenTween.Tests/UpdateDialogTest.cs create mode 100644 OpenTween.Tests/WaitingDialogTest.cs diff --git a/OpenTween.Tests/ApiInfoDialogTest.cs b/OpenTween.Tests/ApiInfoDialogTest.cs new file mode 100644 index 000000000..4abc38a85 --- /dev/null +++ b/OpenTween.Tests/ApiInfoDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class ApiInfoDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new ApiInfoDialog(); + } + } +} diff --git a/OpenTween.Tests/AppendSettingDialogTest.cs b/OpenTween.Tests/AppendSettingDialogTest.cs new file mode 100644 index 000000000..1e604a72e --- /dev/null +++ b/OpenTween.Tests/AppendSettingDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class AppendSettingDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new AppendSettingDialog(); + } + } +} diff --git a/OpenTween.Tests/ApplicationContainerTest.cs b/OpenTween.Tests/ApplicationContainerTest.cs new file mode 100644 index 000000000..d88fd5fbe --- /dev/null +++ b/OpenTween.Tests/ApplicationContainerTest.cs @@ -0,0 +1,41 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License +// for more details. +// +// You should have received a copy of the GNU General public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Setting; +using Xunit; + +namespace OpenTween +{ + public class ApplicationContainerTest + { + [WinFormsFact] + public void Initialize_Test() + { + var settingManager = new SettingManager(""); + using var container = new ApplicationContainer(settingManager); + } + } +} diff --git a/OpenTween.Tests/AtIdSupplementTest.cs b/OpenTween.Tests/AtIdSupplementTest.cs new file mode 100644 index 000000000..0b76bf3d6 --- /dev/null +++ b/OpenTween.Tests/AtIdSupplementTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class AtIdSupplementTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new AtIdSupplement(itemList: new(), startCharacter: ""); + } + } +} diff --git a/OpenTween.Tests/AuthDialogTest.cs b/OpenTween.Tests/AuthDialogTest.cs new file mode 100644 index 000000000..58d5ce4f9 --- /dev/null +++ b/OpenTween.Tests/AuthDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class AuthDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new AuthDialog(); + } + } +} diff --git a/OpenTween.Tests/AuthTypeSelectDialogTest.cs b/OpenTween.Tests/AuthTypeSelectDialogTest.cs new file mode 100644 index 000000000..0e897711c --- /dev/null +++ b/OpenTween.Tests/AuthTypeSelectDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class AuthTypeSelectDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new AuthTypeSelectDialog(); + } + } +} diff --git a/OpenTween.Tests/BingTest.cs b/OpenTween.Tests/BingTest.cs index cb1aa9255..b446d028a 100644 --- a/OpenTween.Tests/BingTest.cs +++ b/OpenTween.Tests/BingTest.cs @@ -36,6 +36,10 @@ namespace OpenTween /// public class BingTest { + [Fact] + public void Initialize_Test() + => new Bing(); + [Theory] [InlineData("af", 0)] [InlineData("sq", 1)] diff --git a/OpenTween.Tests/DetailsListViewTest.cs b/OpenTween.Tests/DetailsListViewTest.cs new file mode 100644 index 000000000..d890a1499 --- /dev/null +++ b/OpenTween.Tests/DetailsListViewTest.cs @@ -0,0 +1,40 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.OpenTweenCustomControl; +using Xunit; + +namespace OpenTween +{ + public class DetailsListViewTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var listView = new DetailsListView(); + } + } +} diff --git a/OpenTween.Tests/EncryptApiKeyDialogTest.cs b/OpenTween.Tests/EncryptApiKeyDialogTest.cs new file mode 100644 index 000000000..cf11ebcce --- /dev/null +++ b/OpenTween.Tests/EncryptApiKeyDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class EncryptApiKeyDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new EncryptApiKeyDialog(); + } + } +} diff --git a/OpenTween.Tests/ErrorReportHandlerTest.cs b/OpenTween.Tests/ErrorReportHandlerTest.cs new file mode 100644 index 000000000..ae78cd620 --- /dev/null +++ b/OpenTween.Tests/ErrorReportHandlerTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class ErrorReportHandlerTest + { + [Fact] + public void Initialize_Test() + { + using var handler = new ErrorReportHandler(); + } + } +} diff --git a/OpenTween.Tests/FilterDialogTest.cs b/OpenTween.Tests/FilterDialogTest.cs new file mode 100644 index 000000000..fee1961b7 --- /dev/null +++ b/OpenTween.Tests/FilterDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class FilterDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new FilterDialog(); + } + } +} diff --git a/OpenTween.Tests/HookGlobalHotkeyTest.cs b/OpenTween.Tests/HookGlobalHotkeyTest.cs new file mode 100644 index 000000000..246b7fff0 --- /dev/null +++ b/OpenTween.Tests/HookGlobalHotkeyTest.cs @@ -0,0 +1,41 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Xunit; + +namespace OpenTween +{ + public class HookGlobalHotkeyTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var form = new Form(); + using var hook = new HookGlobalHotkey(form); + } + } +} diff --git a/OpenTween.Tests/IconAssetsManagerTest.cs b/OpenTween.Tests/IconAssetsManagerTest.cs new file mode 100644 index 000000000..ceb4c3a12 --- /dev/null +++ b/OpenTween.Tests/IconAssetsManagerTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class IconAssetsManagerTest + { + [Fact] + public void Initialize_Test() + { + using var assetsManager = new IconAssetsManager(); + } + } +} diff --git a/OpenTween.Tests/ImageCacheTest.cs b/OpenTween.Tests/ImageCacheTest.cs new file mode 100644 index 000000000..a707e14b9 --- /dev/null +++ b/OpenTween.Tests/ImageCacheTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class ImageCacheTest + { + [Fact] + public void Initialize_Test() + { + using var imageCache = new ImageCache(); + } + } +} diff --git a/OpenTween.Tests/InputTabNameTest.cs b/OpenTween.Tests/InputTabNameTest.cs new file mode 100644 index 000000000..559785a94 --- /dev/null +++ b/OpenTween.Tests/InputTabNameTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class InputTabNameTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new InputTabName(); + } + } +} diff --git a/OpenTween.Tests/InternetSecurityManagerTest.cs b/OpenTween.Tests/InternetSecurityManagerTest.cs new file mode 100644 index 000000000..d5cdb1196 --- /dev/null +++ b/OpenTween.Tests/InternetSecurityManagerTest.cs @@ -0,0 +1,41 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Xunit; + +namespace OpenTween +{ + public class InternetSecurityManagerTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var webBrowser = new WebBrowser(); + var securityManager = new InternetSecurityManager(webBrowser); + } + } +} diff --git a/OpenTween.Tests/ListAvailableTest.cs b/OpenTween.Tests/ListAvailableTest.cs new file mode 100644 index 000000000..b5110941a --- /dev/null +++ b/OpenTween.Tests/ListAvailableTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class ListAvailableTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new ListAvailable(); + } + } +} diff --git a/OpenTween.Tests/ListManageTest.cs b/OpenTween.Tests/ListManageTest.cs new file mode 100644 index 000000000..8f3c9aa0a --- /dev/null +++ b/OpenTween.Tests/ListManageTest.cs @@ -0,0 +1,42 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Api; +using Xunit; + +namespace OpenTween +{ + public class ListManageTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var twitterApi = new TwitterApi(); + using var twitter = new Twitter(twitterApi); + using var dialog = new ListManage(twitter); + } + } +} diff --git a/OpenTween.Tests/LoginDialogTest.cs b/OpenTween.Tests/LoginDialogTest.cs new file mode 100644 index 000000000..8b66cace3 --- /dev/null +++ b/OpenTween.Tests/LoginDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class LoginDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new LoginDialog(); + } + } +} diff --git a/OpenTween.Tests/MemoryImageListTest.cs b/OpenTween.Tests/MemoryImageListTest.cs new file mode 100644 index 000000000..167088d69 --- /dev/null +++ b/OpenTween.Tests/MemoryImageListTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class MemoryImageListTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var imageList = new MemoryImageList(); + } + } +} diff --git a/OpenTween.Tests/MouseWheelMessageFilterTest.cs b/OpenTween.Tests/MouseWheelMessageFilterTest.cs index 3d246ef5c..20c2e84da 100644 --- a/OpenTween.Tests/MouseWheelMessageFilterTest.cs +++ b/OpenTween.Tests/MouseWheelMessageFilterTest.cs @@ -31,6 +31,12 @@ namespace OpenTween { public class MouseWheelMessageFilterTest { + [WinFormsFact] + public void Initialize_Test() + { + using var filter = new MouseWheelMessageFilter(); + } + [Fact] public void ParseMessage_MinusTest() { diff --git a/OpenTween.Tests/MyListsTest.cs b/OpenTween.Tests/MyListsTest.cs new file mode 100644 index 000000000..9d98e5945 --- /dev/null +++ b/OpenTween.Tests/MyListsTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class MyListsTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new MyLists(); + } + } +} diff --git a/OpenTween.Tests/OpenURLTest.cs b/OpenTween.Tests/OpenURLTest.cs new file mode 100644 index 000000000..523080388 --- /dev/null +++ b/OpenTween.Tests/OpenURLTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class OpenURLTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new OpenURL(); + } + } +} diff --git a/OpenTween.Tests/SearchWordDialogTest.cs b/OpenTween.Tests/SearchWordDialogTest.cs new file mode 100644 index 000000000..3a03305b2 --- /dev/null +++ b/OpenTween.Tests/SearchWordDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class SearchWordDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new SearchWordDialog(); + } + } +} diff --git a/OpenTween.Tests/SendErrorReportFormTest.cs b/OpenTween.Tests/SendErrorReportFormTest.cs new file mode 100644 index 000000000..1911f6384 --- /dev/null +++ b/OpenTween.Tests/SendErrorReportFormTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class SendErrorReportFormTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var form = new SendErrorReportForm(); + } + } +} diff --git a/OpenTween.Tests/TimelineListViewDrawerTest.cs b/OpenTween.Tests/TimelineListViewDrawerTest.cs new file mode 100644 index 000000000..3ecbcafb4 --- /dev/null +++ b/OpenTween.Tests/TimelineListViewDrawerTest.cs @@ -0,0 +1,46 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Models; +using OpenTween.OpenTweenCustomControl; +using Xunit; + +namespace OpenTween +{ + public class TimelineListViewDrawerTest + { + [WinFormsFact] + public void Initialize_Test() + { + var tab = new PublicSearchTabModel("hoge"); + using var listView = new DetailsListView(); + var listViewCache = new TimelineListViewCache(listView, tab, new()); + using var imageCache = new ImageCache(); + using var theme = new ThemeManager(new()); + using var listViewDrawer = new TimelineListViewDrawer(listView, tab, listViewCache, imageCache, theme); + } + } +} diff --git a/OpenTween.Tests/TimelineListViewStateTest.cs b/OpenTween.Tests/TimelineListViewStateTest.cs new file mode 100644 index 000000000..eb4d8f4cd --- /dev/null +++ b/OpenTween.Tests/TimelineListViewStateTest.cs @@ -0,0 +1,43 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Models; +using OpenTween.OpenTweenCustomControl; +using Xunit; + +namespace OpenTween +{ + public class TimelineListViewStateTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var listView = new DetailsListView(); + var tab = new PublicSearchTabModel("hoge"); + var listViewState = new TimelineListViewState(listView, tab); + } + } +} diff --git a/OpenTween.Tests/ToolStripLabelHistoryTest.cs b/OpenTween.Tests/ToolStripLabelHistoryTest.cs new file mode 100644 index 000000000..50831fe8f --- /dev/null +++ b/OpenTween.Tests/ToolStripLabelHistoryTest.cs @@ -0,0 +1,40 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.OpenTweenCustomControl; +using Xunit; + +namespace OpenTween +{ + public class ToolStripLabelHistoryTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var toolStripLabelHistory = new ToolStripLabelHistory(); + } + } +} diff --git a/OpenTween.Tests/TweenAboutBoxTest.cs b/OpenTween.Tests/TweenAboutBoxTest.cs new file mode 100644 index 000000000..3deee155d --- /dev/null +++ b/OpenTween.Tests/TweenAboutBoxTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class TweenAboutBoxTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new TweenAboutBox(); + } + } +} diff --git a/OpenTween.Tests/TweetDetailsViewTest.cs b/OpenTween.Tests/TweetDetailsViewTest.cs index 6aeceea2c..928c35c6b 100644 --- a/OpenTween.Tests/TweetDetailsViewTest.cs +++ b/OpenTween.Tests/TweetDetailsViewTest.cs @@ -32,6 +32,12 @@ namespace OpenTween { public class TweetDetailsViewTest { + [WinFormsFact] + public void Initialize_Test() + { + using var detailsView = new TweetDetailsView(); + } + [Fact] public void FormatQuoteTweetHtml_PostClassTest() { diff --git a/OpenTween.Tests/TweetThumbnailControlTest.cs b/OpenTween.Tests/TweetThumbnailControlTest.cs new file mode 100644 index 000000000..e58fbf326 --- /dev/null +++ b/OpenTween.Tests/TweetThumbnailControlTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class TweetThumbnailControlTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var control = new TweetThumbnailControl(); + } + } +} diff --git a/OpenTween.Tests/UpdateDialogTest.cs b/OpenTween.Tests/UpdateDialogTest.cs new file mode 100644 index 000000000..12e6436f8 --- /dev/null +++ b/OpenTween.Tests/UpdateDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class UpdateDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new UpdateDialog(); + } + } +} diff --git a/OpenTween.Tests/WaitingDialogTest.cs b/OpenTween.Tests/WaitingDialogTest.cs new file mode 100644 index 000000000..2f19ce28e --- /dev/null +++ b/OpenTween.Tests/WaitingDialogTest.cs @@ -0,0 +1,39 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class WaitingDialogTest + { + [WinFormsFact] + public void Initialize_Test() + { + using var dialog = new WaitingDialog(); + } + } +} From 2462c65cf6b58e2f5bc5c0abbcd91b0a871c77ae Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 00:10:30 +0900 Subject: [PATCH 11/76] =?UTF-8?q?OTBaseForm.ScaleChildControl=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=81=AB=E5=AF=BE=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/OTBaseFormTest.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/OpenTween.Tests/OTBaseFormTest.cs b/OpenTween.Tests/OTBaseFormTest.cs index e89f26148..4311fcaaa 100644 --- a/OpenTween.Tests/OTBaseFormTest.cs +++ b/OpenTween.Tests/OTBaseFormTest.cs @@ -125,5 +125,28 @@ public void ScaleChildControl_VScrollBarTest() Assert.Equal(40, scrollBar.Width); } + + [WinFormsFact] + public void ScaleChildControl_ImageListTest() + { + using var imageList = new ImageList() { ImageSize = new(16, 16) }; + OTBaseForm.ScaleChildControl(imageList, new SizeF(2.0f, 2.0f)); + + Assert.Equal(new(32, 32), imageList.ImageSize); + } + + [Fact] + public void ScaleBy_SizeTest() + { + var factor = new SizeF(2.0f, 2.0f); + Assert.Equal(new(32, 32), OTBaseForm.ScaleBy(factor, new(16, 16))); + } + + [Fact] + public void ScaleBy_IntegerTest() + { + var factor = 2.0f; + Assert.Equal(32, OTBaseForm.ScaleBy(factor, 16)); + } } } From 5bb42e683f8db1231a15be04f83adf834ebc706c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 00:11:08 +0900 Subject: [PATCH 12/76] =?UTF-8?q?Control.TryInvoke=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=AB=E5=AF=BE=E3=81=99=E3=82=8B=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/ExtensionsTest.cs | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/OpenTween.Tests/ExtensionsTest.cs b/OpenTween.Tests/ExtensionsTest.cs index 7a6bbd90b..38f67ef61 100644 --- a/OpenTween.Tests/ExtensionsTest.cs +++ b/OpenTween.Tests/ExtensionsTest.cs @@ -26,6 +26,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; using Moq; using Xunit; @@ -125,6 +126,60 @@ public void GetCodepointCount_ErrorTest() Assert.Throws(() => "abc".GetCodepointCount(2, 1)); } + [WinFormsFact] + public async Task TryInvoke_InvokeNotRequiredTest() + { + var tcs = new TaskCompletionSource(); + using var control = new Control(); + control.CreateControl(); + + var uiThreadId = Thread.CurrentThread.ManagedThreadId; + var ret = control.TryInvoke(() => + { + Assert.Equal(uiThreadId, Thread.CurrentThread.ManagedThreadId); + tcs.SetResult(1); + }); + Assert.True(ret); + + await tcs.Task; + } + + [WinFormsFact] + public async Task TryInvoke_InvokeRequiredTest() + { + var tcs = new TaskCompletionSource(); + using var control = new Control(); + control.CreateControl(); + + var uiThreadId = Thread.CurrentThread.ManagedThreadId; + await Task.Run(() => + { + var workerThreadId = Thread.CurrentThread.ManagedThreadId; + var ret = control.TryInvoke(() => + { + Assert.NotEqual(workerThreadId, Thread.CurrentThread.ManagedThreadId); + Assert.Equal(uiThreadId, Thread.CurrentThread.ManagedThreadId); + tcs.SetResult(1); + }); + Assert.True(ret); + }); + + await tcs.Task; + } + + [WinFormsFact] + public void TryInvoke_DisposedTest() + { + var control = new Control(); + control.CreateControl(); + control.Dispose(); + + var ret = control.TryInvoke( + () => Assert.Fail("should not be called") + ); + Assert.False(ret); + } + [Fact] public async Task ForEachAsync_Test() { From 44d74b5239c0fc26531b72aeeb5b96ed4c4db8da Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 17:58:40 +0900 Subject: [PATCH 13/76] =?UTF-8?q?TweenMain=E3=81=AE=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=A9=E3=82=AF=E3=82=BF=E3=82=92=E5=85=88?= =?UTF-8?q?=E9=A0=AD=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 392 ++++++++++++++++++++++----------------------- 1 file changed, 196 insertions(+), 196 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 969462a2d..1f0415da1 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -264,202 +264,6 @@ private readonly record struct StatusTextHistory( private readonly HookGlobalHotkey hookGlobalHotkey; - private void TweenMain_Activated(object sender, EventArgs e) - { - // 画面がアクティブになったら、発言欄の背景色戻す - if (this.StatusText.Focused) - { - this.StatusText_Enter(this.StatusText, System.EventArgs.Empty); - } - } - - private bool disposed = false; - - /// - /// 使用中のリソースをすべてクリーンアップします。 - /// - /// マネージ リソースが破棄される場合 true、破棄されない場合は false です。 - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (this.disposed) - return; - - if (disposing) - { - this.components?.Dispose(); - - // 後始末 - this.SearchDialog.Dispose(); - this.urlDialog.Dispose(); - this.themeManager.Dispose(); - this.sfTab.Dispose(); - - this.timelineScheduler.Dispose(); - this.workerCts.Cancel(); - this.thumbnailTokenSource?.Dispose(); - - this.hookGlobalHotkey.Dispose(); - } - - // 終了時にRemoveHandlerしておかないとメモリリークする - // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx - Microsoft.Win32.SystemEvents.PowerModeChanged -= this.SystemEvents_PowerModeChanged; - Microsoft.Win32.SystemEvents.TimeChanged -= this.SystemEvents_TimeChanged; - - this.disposed = true; - } - - private void InitColumns(ListView list, bool startup) - { - this.InitColumnText(); - - ColumnHeader[]? columns = null; - try - { - if (this.Use2ColumnsMode) - { - columns = new[] - { - new ColumnHeader(), // アイコン - new ColumnHeader(), // 本文 - }; - - columns[0].Text = this.columnText[0]; - columns[1].Text = this.columnText[2]; - - if (startup) - { - var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width; - - columns[0].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[0]); - columns[1].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[2]); - columns[0].DisplayIndex = 0; - columns[1].DisplayIndex = 1; - } - else - { - var idx = 0; - foreach (var curListColumn in this.CurrentListView.Columns.Cast()) - { - columns[idx].Width = curListColumn.Width; - columns[idx].DisplayIndex = curListColumn.DisplayIndex; - idx++; - } - } - } - else - { - columns = new[] - { - new ColumnHeader(), // アイコン - new ColumnHeader(), // ニックネーム - new ColumnHeader(), // 本文 - new ColumnHeader(), // 日付 - new ColumnHeader(), // ユーザID - new ColumnHeader(), // 未読 - new ColumnHeader(), // マーク&プロテクト - new ColumnHeader(), // ソース - }; - - foreach (var i in Enumerable.Range(0, columns.Length)) - columns[i].Text = this.columnText[i]; - - if (startup) - { - var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width; - - foreach (var (column, index) in columns.WithIndex()) - { - column.Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[index]); - column.DisplayIndex = this.settings.Local.ColumnsOrder[index]; - } - } - else - { - var idx = 0; - foreach (var curListColumn in this.CurrentListView.Columns.Cast()) - { - columns[idx].Width = curListColumn.Width; - columns[idx].DisplayIndex = curListColumn.DisplayIndex; - idx++; - } - } - } - - list.Columns.AddRange(columns); - - columns = null; - } - finally - { - if (columns != null) - { - foreach (var column in columns) - column?.Dispose(); - } - } - } - - private void InitColumnText() - { - this.columnText[0] = ""; - this.columnText[1] = Properties.Resources.AddNewTabText2; - this.columnText[2] = Properties.Resources.AddNewTabText3; - this.columnText[3] = Properties.Resources.AddNewTabText4_2; - this.columnText[4] = Properties.Resources.AddNewTabText5; - this.columnText[5] = ""; - this.columnText[6] = ""; - this.columnText[7] = "Source"; - - this.columnOrgText[0] = ""; - this.columnOrgText[1] = Properties.Resources.AddNewTabText2; - this.columnOrgText[2] = Properties.Resources.AddNewTabText3; - this.columnOrgText[3] = Properties.Resources.AddNewTabText4_2; - this.columnOrgText[4] = Properties.Resources.AddNewTabText5; - this.columnOrgText[5] = ""; - this.columnOrgText[6] = ""; - this.columnOrgText[7] = "Source"; - - var c = this.statuses.SortMode switch - { - ComparerMode.Nickname => 1, // ニックネーム - ComparerMode.Data => 2, // 本文 - ComparerMode.Id => 3, // 時刻=発言Id - ComparerMode.Name => 4, // 名前 - ComparerMode.Source => 7, // Source - _ => 0, - }; - - if (this.Use2ColumnsMode) - { - if (this.statuses.SortOrder == SortOrder.Descending) - { - // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE - this.columnText[2] = this.columnOrgText[2] + "▾"; - } - else - { - // U+25B4 BLACK UP-POINTING SMALL TRIANGLE - this.columnText[2] = this.columnOrgText[2] + "▴"; - } - } - else - { - if (this.statuses.SortOrder == SortOrder.Descending) - { - // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE - this.columnText[c] = this.columnOrgText[c] + "▾"; - } - else - { - // U+25B4 BLACK UP-POINTING SMALL TRIANGLE - this.columnText[c] = this.columnOrgText[c] + "▴"; - } - } - } - public TweenMain( SettingManager settingManager, TabInformations tabInfo, @@ -785,6 +589,202 @@ ThumbnailGenerator thumbGenerator } } + private void TweenMain_Activated(object sender, EventArgs e) + { + // 画面がアクティブになったら、発言欄の背景色戻す + if (this.StatusText.Focused) + { + this.StatusText_Enter(this.StatusText, System.EventArgs.Empty); + } + } + + private bool disposed = false; + + /// + /// 使用中のリソースをすべてクリーンアップします。 + /// + /// マネージ リソースが破棄される場合 true、破棄されない場合は false です。 + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (this.disposed) + return; + + if (disposing) + { + this.components?.Dispose(); + + // 後始末 + this.SearchDialog.Dispose(); + this.urlDialog.Dispose(); + this.themeManager.Dispose(); + this.sfTab.Dispose(); + + this.timelineScheduler.Dispose(); + this.workerCts.Cancel(); + this.thumbnailTokenSource?.Dispose(); + + this.hookGlobalHotkey.Dispose(); + } + + // 終了時にRemoveHandlerしておかないとメモリリークする + // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx + Microsoft.Win32.SystemEvents.PowerModeChanged -= this.SystemEvents_PowerModeChanged; + Microsoft.Win32.SystemEvents.TimeChanged -= this.SystemEvents_TimeChanged; + + this.disposed = true; + } + + private void InitColumns(ListView list, bool startup) + { + this.InitColumnText(); + + ColumnHeader[]? columns = null; + try + { + if (this.Use2ColumnsMode) + { + columns = new[] + { + new ColumnHeader(), // アイコン + new ColumnHeader(), // 本文 + }; + + columns[0].Text = this.columnText[0]; + columns[1].Text = this.columnText[2]; + + if (startup) + { + var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width; + + columns[0].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[0]); + columns[1].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[2]); + columns[0].DisplayIndex = 0; + columns[1].DisplayIndex = 1; + } + else + { + var idx = 0; + foreach (var curListColumn in this.CurrentListView.Columns.Cast()) + { + columns[idx].Width = curListColumn.Width; + columns[idx].DisplayIndex = curListColumn.DisplayIndex; + idx++; + } + } + } + else + { + columns = new[] + { + new ColumnHeader(), // アイコン + new ColumnHeader(), // ニックネーム + new ColumnHeader(), // 本文 + new ColumnHeader(), // 日付 + new ColumnHeader(), // ユーザID + new ColumnHeader(), // 未読 + new ColumnHeader(), // マーク&プロテクト + new ColumnHeader(), // ソース + }; + + foreach (var i in Enumerable.Range(0, columns.Length)) + columns[i].Text = this.columnText[i]; + + if (startup) + { + var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width; + + foreach (var (column, index) in columns.WithIndex()) + { + column.Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[index]); + column.DisplayIndex = this.settings.Local.ColumnsOrder[index]; + } + } + else + { + var idx = 0; + foreach (var curListColumn in this.CurrentListView.Columns.Cast()) + { + columns[idx].Width = curListColumn.Width; + columns[idx].DisplayIndex = curListColumn.DisplayIndex; + idx++; + } + } + } + + list.Columns.AddRange(columns); + + columns = null; + } + finally + { + if (columns != null) + { + foreach (var column in columns) + column?.Dispose(); + } + } + } + + private void InitColumnText() + { + this.columnText[0] = ""; + this.columnText[1] = Properties.Resources.AddNewTabText2; + this.columnText[2] = Properties.Resources.AddNewTabText3; + this.columnText[3] = Properties.Resources.AddNewTabText4_2; + this.columnText[4] = Properties.Resources.AddNewTabText5; + this.columnText[5] = ""; + this.columnText[6] = ""; + this.columnText[7] = "Source"; + + this.columnOrgText[0] = ""; + this.columnOrgText[1] = Properties.Resources.AddNewTabText2; + this.columnOrgText[2] = Properties.Resources.AddNewTabText3; + this.columnOrgText[3] = Properties.Resources.AddNewTabText4_2; + this.columnOrgText[4] = Properties.Resources.AddNewTabText5; + this.columnOrgText[5] = ""; + this.columnOrgText[6] = ""; + this.columnOrgText[7] = "Source"; + + var c = this.statuses.SortMode switch + { + ComparerMode.Nickname => 1, // ニックネーム + ComparerMode.Data => 2, // 本文 + ComparerMode.Id => 3, // 時刻=発言Id + ComparerMode.Name => 4, // 名前 + ComparerMode.Source => 7, // Source + _ => 0, + }; + + if (this.Use2ColumnsMode) + { + if (this.statuses.SortOrder == SortOrder.Descending) + { + // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE + this.columnText[2] = this.columnOrgText[2] + "▾"; + } + else + { + // U+25B4 BLACK UP-POINTING SMALL TRIANGLE + this.columnText[2] = this.columnOrgText[2] + "▴"; + } + } + else + { + if (this.statuses.SortOrder == SortOrder.Descending) + { + // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE + this.columnText[c] = this.columnOrgText[c] + "▾"; + } + else + { + // U+25B4 BLACK UP-POINTING SMALL TRIANGLE + this.columnText[c] = this.columnOrgText[c] + "▴"; + } + } + } + private void InitDetailHtmlFormat() { var htmlTemplate = this.settings.Common.IsMonospace ? DetailHtmlFormatTemplateMono : DetailHtmlFormatTemplateNormal; From 85cbd7506de803fe920d1ea6e50f2a5bcb4ec130 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 19:26:06 +0900 Subject: [PATCH 14/76] =?UTF-8?q?TweenMain=E5=86=85=E3=81=A7=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=81=99=E3=82=8B=E3=82=BF=E3=82=A4=E3=83=9E=E3=83=BC?= =?UTF-8?q?=E3=82=92Shown=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E3=81=BE?= =?UTF-8?q?=E3=81=A7=E7=84=A1=E5=8A=B9=E3=81=AA=E7=8A=B6=E6=85=8B=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 1f0415da1..72b4c49cc 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -359,7 +359,6 @@ ThumbnailGenerator thumbGenerator var imgazyobizinet = this.thumbGenerator.ImgAzyobuziNet; imgazyobizinet.Enabled = this.settings.Common.EnableImgAzyobuziNet; imgazyobizinet.DisabledInDM = this.settings.Common.ImgAzyobuziNetDisabledInDM; - imgazyobizinet.AutoUpdate = true; Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.tw.Api.Connection; @@ -7979,6 +7978,7 @@ private void SelectListItem(DetailsListView lView, int index) private async void TweenMain_Shown(object sender, EventArgs e) { this.NotifyIcon1.Visible = true; + this.StartTimers(); if (this.IsNetworkAvailable()) { @@ -8059,8 +8059,14 @@ private async void TweenMain_Shown(object sender, EventArgs e) } this.initial = false; + } + + private void StartTimers() + { + if (!this.StopRefreshAllMenuItem.Checked) + this.timelineScheduler.Enabled = true; - this.timelineScheduler.Enabled = true; + this.thumbGenerator.ImgAzyobuziNet.AutoUpdate = true; } private async Task DoGetFollowersMenu() From cbcd694ced8858aaed9172f03d0c495725c30c08 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 19:57:39 +0900 Subject: [PATCH 15/76] =?UTF-8?q?VerifyCredentials=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=AE=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97?= =?UTF-8?q?=E3=82=92TweenMain=E3=82=B3=E3=83=B3=E3=82=B9=E3=83=88=E3=83=A9?= =?UTF-8?q?=E3=82=AF=E3=82=BF=E3=81=AE=E5=A4=96=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/DebounceTimerTest.cs | 57 ++++++++++++++++++++++++++++ OpenTween/ApplicationEvents.cs | 30 +++++++++++++++ OpenTween/DebounceTimer.cs | 19 ++++++++++ OpenTween/Tween.cs | 28 +------------- 4 files changed, 108 insertions(+), 26 deletions(-) diff --git a/OpenTween.Tests/DebounceTimerTest.cs b/OpenTween.Tests/DebounceTimerTest.cs index aadda5b2c..cf570bf9e 100644 --- a/OpenTween.Tests/DebounceTimerTest.cs +++ b/OpenTween.Tests/DebounceTimerTest.cs @@ -62,6 +62,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -119,6 +121,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -159,6 +163,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -215,6 +221,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -272,6 +280,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -312,6 +322,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -385,6 +397,8 @@ Task Callback() using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); var mockTimer = debouncing.MockTimer; + debouncing.Enabled = true; + Assert.Equal(0, count); Assert.False(mockTimer.IsTimerRunning); @@ -418,5 +432,48 @@ Task Callback() Assert.False(mockTimer.IsTimerRunning); } } + + [Fact] + public async Task Call_DisabledTest() + { + using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0))) + { + static Task Callback() + => Task.CompletedTask; + + var interval = TimeSpan.FromMinutes(2); + var maxWait = TimeSpan.MaxValue; + using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); + var mockTimer = debouncing.MockTimer; + + debouncing.Enabled = false; + + await debouncing.Call(); + Assert.False(mockTimer.IsTimerRunning); + } + } + + [Fact] + public async Task DisabledWhileTimerIsRunning() + { + using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0))) + { + static Task Callback() + => Task.CompletedTask; + + var interval = TimeSpan.FromMinutes(2); + var maxWait = TimeSpan.MaxValue; + using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true); + var mockTimer = debouncing.MockTimer; + + debouncing.Enabled = true; + + await debouncing.Call(); + Assert.True(mockTimer.IsTimerRunning); + + debouncing.Enabled = false; + Assert.False(mockTimer.IsTimerRunning); + } + } } } diff --git a/OpenTween/ApplicationEvents.cs b/OpenTween/ApplicationEvents.cs index 39e75494d..539639ccc 100644 --- a/OpenTween/ApplicationEvents.cs +++ b/OpenTween/ApplicationEvents.cs @@ -94,6 +94,8 @@ public static int Main(string[] args) return 1; // 設定が完了しなかったため終了 } + SetupTwitter(container.Twitter, settings); + Application.Run(container.MainForm); return 0; @@ -136,5 +138,33 @@ private static bool ShowSettingsDialog(SettingManager settings, IconAssetsManage settings.SaveAll(); return true; } + + private static void SetupTwitter(Twitter tw, SettingManager settings) + { + var account = settings.Common.SelectedAccount; + if (account != null) + tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId); + else + tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L); + + tw.RestrictFavCheck = settings.Common.RestrictFavCheck; + tw.ReadOwnPost = settings.Common.ReadOwnPost; + + // アクセストークンが有効であるか確認する + // ここが Twitter API への最初のアクセスになるようにすること + try + { + tw.VerifyCredentials(); + } + catch (WebApiException ex) + { + MessageBox.Show( + string.Format(Properties.Resources.StartupAuthError_Text, ex.Message), + ApplicationSettings.ApplicationName, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + } } } diff --git a/OpenTween/DebounceTimer.cs b/OpenTween/DebounceTimer.cs index cc728cbb0..033138be3 100644 --- a/OpenTween/DebounceTimer.cs +++ b/OpenTween/DebounceTimer.cs @@ -39,10 +39,26 @@ public class DebounceTimer : IDisposable private readonly Func timerCallback; private readonly object lockObject = new(); + private bool enabled = false; private DateTimeUtc lastCall; private bool calledSinceLastInvoke; private bool refreshTimerEnabled; + public bool Enabled + { + get => this.enabled; + set + { + if (value == this.enabled) + return; + + this.enabled = value; + + if (!value) + this.debouncingTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + } + } + public TimeSpan Interval { get; } public bool InvokeLeading { get; } @@ -66,6 +82,9 @@ protected virtual ITimer CreateTimer(Func callback) public async Task Call() { + if (!this.Enabled) + return; + bool startTimer, invoke; lock (this.lockObject) { diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 72b4c49cc..8a40abaae 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -326,34 +326,8 @@ ThumbnailGenerator thumbGenerator // 現在の DPI と設定保存時の DPI との比を取得する var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions); - // 認証関連 - var account = this.settings.Common.SelectedAccount; - if (account != null) - this.tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId); - else - this.tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L); - this.initial = true; - this.tw.RestrictFavCheck = this.settings.Common.RestrictFavCheck; - this.tw.ReadOwnPost = this.settings.Common.ReadOwnPost; - - // アクセストークンが有効であるか確認する - // ここが Twitter API への最初のアクセスになるようにすること - try - { - this.tw.VerifyCredentials(); - } - catch (WebApiException ex) - { - MessageBox.Show( - this, - string.Format(Properties.Resources.StartupAuthError_Text, ex.Message), - ApplicationSettings.ApplicationName, - MessageBoxButtons.OK, - MessageBoxIcon.Warning); - } - // サムネイル関連の初期化 // プロキシ設定等の通信まわりの初期化が済んでから処理する var imgazyobizinet = this.thumbGenerator.ImgAzyobuziNet; @@ -8066,6 +8040,8 @@ private void StartTimers() if (!this.StopRefreshAllMenuItem.Checked) this.timelineScheduler.Enabled = true; + this.selectionDebouncer.Enabled = true; + this.saveConfigDebouncer.Enabled = true; this.thumbGenerator.ImgAzyobuziNet.AutoUpdate = true; } From 0d7049db8bea020c6d1f483cbd87b70bfb0d6814 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 4 Dec 2023 20:18:10 +0900 Subject: [PATCH 16/76] =?UTF-8?q?=E5=88=9D=E5=9B=9E=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E6=99=82=E3=81=ABHashStripSplitButton=E3=81=AE=E3=83=89?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=83=97=E3=83=80=E3=82=A6=E3=83=B3=E3=83=A1?= =?UTF-8?q?=E3=83=8B=E3=83=A5=E3=83=BC=E3=82=92=E5=B1=95=E9=96=8B=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92Shown=E3=82=A4=E3=83=99?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 8a40abaae..08ff6a08e 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -554,12 +554,6 @@ ThumbnailGenerator thumbGenerator this.ignoreConfigSave = false; this.TweenMain_Resize(this, EventArgs.Empty); - - if (this.settings.IsFirstRun) - { - // 初回起動時だけ右下のメニューを目立たせる - this.HashStripSplitButton.ShowDropDown(); - } } private void TweenMain_Activated(object sender, EventArgs e) @@ -7954,6 +7948,12 @@ private async void TweenMain_Shown(object sender, EventArgs e) this.NotifyIcon1.Visible = true; this.StartTimers(); + if (this.settings.IsFirstRun) + { + // 初回起動時だけ右下のメニューを目立たせる + this.HashStripSplitButton.ShowDropDown(); + } + if (this.IsNetworkAvailable()) { var loadTasks = new TaskCollection(); From 39e84d61781b718e5d527be3a0a5514821df9feb Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 6 Dec 2023 00:58:24 +0900 Subject: [PATCH 17/76] =?UTF-8?q?TweenMain=E5=88=9D=E6=9C=9F=E5=8C=96?= =?UTF-8?q?=E6=99=82=E3=81=AB=E5=BF=85=E8=A6=81=E3=81=AA=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E3=81=AE=E3=81=BFVisible=E3=81=AE=E5=80=A4=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 08ff6a08e..d9492a09c 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -306,7 +306,6 @@ ThumbnailGenerator thumbGenerator this.InitializeShortcuts(); this.ignoreConfigSave = true; - this.Visible = false; this.TraceOutToolStripMenuItem.Checked = MyCommon.TraceFlag; @@ -522,9 +521,9 @@ ThumbnailGenerator thumbGenerator this.SetMainWindowTitle(); this.SetNotifyIconText(); - if (!this.settings.Common.MinimizeToTray || this.WindowState != FormWindowState.Minimized) + if (this.settings.Common.MinimizeToTray && this.WindowState == FormWindowState.Minimized) { - this.Visible = true; + this.Visible = false; } // タイマー設定 From e7ea8f3a262822c1d9b6862dc5d4ff135f4cb6b6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 6 Dec 2023 01:00:06 +0900 Subject: [PATCH 18/76] =?UTF-8?q?=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=82=A6=E3=82=B5=E3=82=A4=E3=82=BA=E7=AD=89=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E5=80=A4=E3=82=92=E9=81=A9=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92Resize=E3=82=A4=E3=83=99=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=8B=E3=82=89=E3=82=B3=E3=83=B3=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=A9=E3=82=AF=E3=82=BF=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 80 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index d9492a09c..5e0a527ab 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -552,7 +552,7 @@ ThumbnailGenerator thumbGenerator this.TimerRefreshIcon.Enabled = false; this.ignoreConfigSave = false; - this.TweenMain_Resize(this, EventArgs.Empty); + this.ApplyLayoutFromSettings(); } private void TweenMain_Activated(object sender, EventArgs e) @@ -7180,56 +7180,56 @@ private void TweenMain_Resize(object sender, EventArgs e) { this.Visible = false; } - if (this.initialLayout && this.settings.Local != null && this.WindowState == FormWindowState.Normal && this.Visible) + if (this.WindowState != FormWindowState.Minimized) { - // 現在の DPI と設定保存時の DPI との比を取得する - var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions); + this.formWindowState = this.WindowState; + } + } - this.ClientSize = ScaleBy(configScaleFactor, this.settings.Local.FormSize); + private void ApplyLayoutFromSettings() + { + // 現在の DPI と設定保存時の DPI との比を取得する + var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions); - // Splitterの位置設定 - var splitterDistance = ScaleBy(configScaleFactor.Height, this.settings.Local.SplitterDistance); - if (splitterDistance > this.SplitContainer1.Panel1MinSize && - splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth) - { - this.SplitContainer1.SplitterDistance = splitterDistance; - } + this.ClientSize = ScaleBy(configScaleFactor, this.settings.Local.FormSize); - // 発言欄複数行 - this.StatusText.Multiline = this.settings.Local.StatusMultiline; - if (this.StatusText.Multiline) - { - var statusTextHeight = ScaleBy(configScaleFactor.Height, this.settings.Local.StatusTextHeight); - var dis = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth; - if (dis > this.SplitContainer2.Panel1MinSize && dis < this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth) - { - this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth; - } - this.StatusText.Height = statusTextHeight; - } - else + // Splitterの位置設定 + var splitterDistance = ScaleBy(configScaleFactor.Height, this.settings.Local.SplitterDistance); + if (splitterDistance > this.SplitContainer1.Panel1MinSize && + splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth) + { + this.SplitContainer1.SplitterDistance = splitterDistance; + } + + // 発言欄複数行 + this.StatusText.Multiline = this.settings.Local.StatusMultiline; + if (this.StatusText.Multiline) + { + var statusTextHeight = ScaleBy(configScaleFactor.Height, this.settings.Local.StatusTextHeight); + var dis = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth; + if (dis > this.SplitContainer2.Panel1MinSize && dis < this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth) { - if (this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth > 0) - { - this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth; - } + this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth; } - - var previewDistance = ScaleBy(configScaleFactor.Width, this.settings.Local.PreviewDistance); - if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth) + this.StatusText.Height = statusTextHeight; + } + else + { + if (this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth > 0) { - this.SplitContainer3.SplitterDistance = previewDistance; + this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth; } - - // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない - this.SplitContainer3.Panel2Collapsed = true; - - this.initialLayout = false; } - if (this.WindowState != FormWindowState.Minimized) + + var previewDistance = ScaleBy(configScaleFactor.Width, this.settings.Local.PreviewDistance); + if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth) { - this.formWindowState = this.WindowState; + this.SplitContainer3.SplitterDistance = previewDistance; } + + // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない + this.SplitContainer3.Panel2Collapsed = true; + this.initialLayout = false; } private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e) From a95b338a3a6530b850fddafc3480232ba2b76d24 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 6 Dec 2023 01:03:35 +0900 Subject: [PATCH 19/76] =?UTF-8?q?TweenMain=E3=81=AE=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=A9=E3=82=AF=E3=82=BF=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/TweenMainTest.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index a7974618c..37a1ac2f3 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -25,7 +25,12 @@ using System.Linq; using System.Text; using System.Windows.Forms; +using OpenTween.Api; using OpenTween.Api.DataModel; +using OpenTween.Connection; +using OpenTween.Models; +using OpenTween.Setting; +using OpenTween.Thumbnail; using Xunit; using Xunit.Extensions; @@ -33,6 +38,27 @@ namespace OpenTween { public class TweenMainTest { + [WinFormsFact] + public void Initialize_Test() + { + var settings = new SettingManager(""); + var tabinfo = new TabInformations(); + using var twitterApi = new TwitterApi(); + using var twitter = new Twitter(twitterApi); + using var imageCache = new ImageCache(); + using var iconAssets = new IconAssetsManager(); + var thumbnailGenerator = new ThumbnailGenerator(new(autoupdate: false)); + var twitterAppToken = new TwitterAppToken + { + AuthType = APIAuthType.OAuth1, + OAuth1CustomConsumerKey = ApiKey.Create("aaa"), + OAuth1CustomConsumerSecret = ApiKey.Create("bbb"), + }; + twitter.Initialize(twitterAppToken, "", "", "", 0L); + + using var tweenMain = new TweenMain(settings, tabinfo, twitter, imageCache, iconAssets, thumbnailGenerator); + } + [Fact] public void GetUrlFromDataObject_XMozUrlTest() { From 900afef5cf784302a444b92e5f5eb77763cffe6e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 02:35:39 +0900 Subject: [PATCH 20/76] =?UTF-8?q?StyleCop.Analyzers=201.2.0-beta.507=20?= =?UTF-8?q?=E3=81=AB=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/OpenTween.Tests.csproj | 2 +- OpenTween/OpenTween.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index aed21e9d0..b35897755 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/OpenTween/OpenTween.csproj b/OpenTween/OpenTween.csproj index fa782f423..688af6852 100644 --- a/OpenTween/OpenTween.csproj +++ b/OpenTween/OpenTween.csproj @@ -642,7 +642,7 @@ 1.0.1 - 1.2.0-beta.406 + 1.2.0-beta.507 runtime; build; native; contentfiles; analyzers; buildtransitive all From 61af1fc9ecc8b7949e1aeee0012bde6fa545d4bb Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 02:36:16 +0900 Subject: [PATCH 21/76] =?UTF-8?q?required=E4=BF=AE=E9=A3=BE=E5=AD=90?= =?UTF-8?q?=E3=81=AE=E9=A0=86=E5=BA=8F=E3=82=92=E4=BF=AE=E6=AD=A3=20(SA120?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Api/GraphQL/CreateRetweetRequest.cs | 2 +- OpenTween/Api/GraphQL/CreateTweetRequest.cs | 2 +- OpenTween/Api/GraphQL/DeleteRetweetRequest.cs | 2 +- OpenTween/Api/GraphQL/DeleteTweetRequest.cs | 2 +- OpenTween/Api/GraphQL/TweetDetailRequest.cs | 2 +- OpenTween/Api/GraphQL/UserByScreenNameRequest.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs index b04388e95..33c579df2 100644 --- a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs @@ -40,7 +40,7 @@ public class CreateRetweetRequest { private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet"); - required public TwitterStatusId TweetId { get; set; } + public required TwitterStatusId TweetId { get; set; } public string CreateRequestBody() { diff --git a/OpenTween/Api/GraphQL/CreateTweetRequest.cs b/OpenTween/Api/GraphQL/CreateTweetRequest.cs index d0bff8655..d2b0f229c 100644 --- a/OpenTween/Api/GraphQL/CreateTweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateTweetRequest.cs @@ -42,7 +42,7 @@ public class CreateTweetRequest { private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/tTsjMKyhajZvK4q76mpIBg/CreateTweet"); - required public string TweetText { get; set; } + public required string TweetText { get; set; } public TwitterStatusId? InReplyToTweetId { get; set; } diff --git a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs index c2b61d34e..ab5b0aedc 100644 --- a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs @@ -36,7 +36,7 @@ public class DeleteRetweetRequest { private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet"); - required public TwitterStatusId SourceTweetId { get; set; } + public required TwitterStatusId SourceTweetId { get; set; } public string CreateRequestBody() { diff --git a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs index c6962830f..feeee14eb 100644 --- a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs @@ -36,7 +36,7 @@ public class DeleteTweetRequest { private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet"); - required public TwitterStatusId TweetId { get; set; } + public required TwitterStatusId TweetId { get; set; } public string CreateRequestBody() { diff --git a/OpenTween/Api/GraphQL/TweetDetailRequest.cs b/OpenTween/Api/GraphQL/TweetDetailRequest.cs index 009e1041f..496e2f47c 100644 --- a/OpenTween/Api/GraphQL/TweetDetailRequest.cs +++ b/OpenTween/Api/GraphQL/TweetDetailRequest.cs @@ -42,7 +42,7 @@ public class TweetDetailRequest private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/-Ls3CrSQNo2fRKH6i6Na1A/TweetDetail"); - required public TwitterStatusId FocalTweetId { get; set; } + public required TwitterStatusId FocalTweetId { get; set; } public Dictionary CreateParameters() { diff --git a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs index f261924b6..8d5607ef5 100644 --- a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs +++ b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs @@ -41,7 +41,7 @@ public class UserByScreenNameRequest private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/xc8f1g7BYqr6VTzTbvNlGw/UserByScreenName"); - required public string ScreenName { get; set; } + public required string ScreenName { get; set; } public Dictionary CreateParameters() { From 04fe35c71bbd1342b74685a1f02b94a5e1e162f6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 02:49:46 +0900 Subject: [PATCH 22/76] =?UTF-8?q?xUnit.net=202.6.2=20=E3=81=AB=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 4 ++-- OpenTween.Tests/OpenTween.Tests.csproj | 6 +++--- OpenTween.Tests/TestUtils.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3829b7fe6..ed50570db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,10 +49,10 @@ jobs: shell: pwsh run: | $altCoverVersion = '8.6.61' - $xunitVersion = '2.4.2' + $xunitVersion = '2.6.2' $targetFramework = 'net48' $altCoverPath = "$($env:NUGET_PACKAGES)\altcover\$($altCoverVersion)\tools\net472\AltCover.exe" - $xunitPath = "$($env:NUGET_PACKAGES)\xunit.runner.console\$($xunitVersion)\tools\net472\xunit.console.exe" + $xunitPath = "$($env:NUGET_PACKAGES)\xunit.runner.console\$($xunitVersion)\tools\net481\xunit.console.exe" $p = Start-Process ` -FilePath $altCoverPath ` diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index b35897755..77ed422b6 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -32,12 +32,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/OpenTween.Tests/TestUtils.cs b/OpenTween.Tests/TestUtils.cs index e7fe87b8a..c1c4e8869 100644 --- a/OpenTween.Tests/TestUtils.cs +++ b/OpenTween.Tests/TestUtils.cs @@ -68,7 +68,7 @@ void Handler(object s, T e) await testCode().ConfigureAwait(false); if (raisedEvent != null) - throw new Xunit.Sdk.RaisesException(typeof(void), raisedEvent.GetType()); + throw Xunit.Sdk.RaisesException.ForIncorrectType(typeof(void), raisedEvent.GetType()); } finally { @@ -81,7 +81,7 @@ public static void NotPropertyChanged(INotifyPropertyChanged @object, string pro void Handler(object s, PropertyChangedEventArgs e) { if (s == @object && e.PropertyName == propertyName) - throw new Xunit.Sdk.PropertyChangedException(propertyName); + throw Xunit.Sdk.PropertyChangedException.ForUnsetProperty(propertyName); } try From d78a4740bad87f264bc262985b829c4432c3c68b Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 03:07:59 +0900 Subject: [PATCH 23/76] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E5=86=85=E3=81=A7=20ConfigureAwait(false)=20?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=AA=E3=81=84=20(xUnit?= =?UTF-8?q?1030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://xunit.net/xunit.analyzers/rules/xUnit1030 --- OpenTween.Tests/Api/BitlyApiTest.cs | 15 +- .../Api/GraphQL/CreateRetweetRequestTest.cs | 2 +- .../Api/GraphQL/CreateTweetRequestTest.cs | 6 +- .../Api/GraphQL/DeleteRetweetRequestTest.cs | 2 +- .../Api/GraphQL/DeleteTweetRequestTest.cs | 2 +- .../ListLatestTweetsTimelineRequestTest.cs | 4 +- .../Api/GraphQL/SearchTimelineRequestTest.cs | 4 +- .../Api/GraphQL/TweetDetailRequestTest.cs | 2 +- .../GraphQL/UserByScreenNameRequestTest.cs | 2 +- .../UserTweetsAndRepliesRequestTest.cs | 4 +- OpenTween.Tests/Api/ImgurApiTest.cs | 3 +- .../Api/MicrosoftTranslatorApiTest.cs | 18 +-- OpenTween.Tests/Api/MobypictureApiTest.cs | 3 +- OpenTween.Tests/Api/TwitterApiTest.cs | 147 ++++++------------ .../Api/TwitterV2/GetTimelineRequestTest.cs | 2 +- OpenTween.Tests/Connection/LazyJsonTest.cs | 11 +- .../Connection/OAuthHandlerTest.cs | 9 +- .../Connection/TwitterApiConnectionTest.cs | 66 ++++---- OpenTween.Tests/MemoryImageTest.cs | 18 +-- OpenTween.Tests/ShortcutCommandTest.cs | 6 +- .../Services/FoursquareCheckinTest.cs | 3 +- .../Thumbnail/Services/PbsTwimgComTest.cs | 6 +- .../Thumbnail/Services/TinamiTest.cs | 3 +- .../Thumbnail/Services/TumblrTest.cs | 9 +- OpenTween.Tests/TweetThumbnailTest.cs | 18 +-- 25 files changed, 134 insertions(+), 231 deletions(-) diff --git a/OpenTween.Tests/Api/BitlyApiTest.cs b/OpenTween.Tests/Api/BitlyApiTest.cs index b8c7f4a66..73c295e46 100644 --- a/OpenTween.Tests/Api/BitlyApiTest.cs +++ b/OpenTween.Tests/Api/BitlyApiTest.cs @@ -60,8 +60,7 @@ public async Task ShortenAsync_OAuth2Test() bitly.EndUserAccessToken = "hogehoge"; - var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly") - .ConfigureAwait(false); + var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly"); Assert.Equal("http://bit.ly/foo", result.OriginalString); Assert.Equal(0, mockHandler.QueueCount); @@ -96,8 +95,7 @@ public async Task ShortenAsync_LegacyApiKeyTest() bitly.EndUserLoginName = "username"; bitly.EndUserApiKey = "hogehoge"; - var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly") - .ConfigureAwait(false); + var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly"); Assert.Equal("http://bit.ly/foo", result.OriginalString); Assert.Equal(0, mockHandler.QueueCount); @@ -122,8 +120,7 @@ public async Task GetAccessTokenAsync_Test() x.Headers.Authorization.Parameter ); - var body = await x.Content.ReadAsStringAsync() - .ConfigureAwait(false); + var body = await x.Content.ReadAsStringAsync(); var query = HttpUtility.ParseQueryString(body); Assert.Equal("password", query["grant_type"]); @@ -136,8 +133,7 @@ public async Task GetAccessTokenAsync_Test() }; }); - var result = await bitly.GetAccessTokenAsync("hogehoge", "tetete") - .ConfigureAwait(false); + var result = await bitly.GetAccessTokenAsync("hogehoge", "tetete"); Assert.Equal("abcdefg", result); Assert.Equal(0, mockHandler.QueueCount); @@ -158,8 +154,7 @@ public async Task GetAccessTokenAsync_ErrorResponseTest() }; }); - await Assert.ThrowsAsync(() => bitly.GetAccessTokenAsync("hogehoge", "tetete")) - .ConfigureAwait(false); + await Assert.ThrowsAsync(() => bitly.GetAccessTokenAsync("hogehoge", "tetete")); Assert.Equal(0, mockHandler.QueueCount); } diff --git a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs index 40f4db801..418d38f7f 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs @@ -54,7 +54,7 @@ public async Task Send_Test() TweetId = new("12345"), }; - var tweetId = await request.Send(mock.Object).ConfigureAwait(false); + var tweetId = await request.Send(mock.Object); Assert.Equal("1617128268548964354", tweetId.Id); mock.VerifyAll(); diff --git a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs index 0565e4793..ef5d8e85d 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs @@ -56,7 +56,7 @@ public async Task Send_Test() TweetText = "tetete", }; - var status = await request.Send(mock.Object).ConfigureAwait(false); + var status = await request.Send(mock.Object); Assert.Equal("1680534146492317696", status.IdStr); mock.VerifyAll(); @@ -83,7 +83,7 @@ public async Task Send_ReplyTest() InReplyToTweetId = new("12345"), ExcludeReplyUserIds = new[] { "11111", "22222" }, }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } @@ -107,7 +107,7 @@ public async Task Send_MediaTest() TweetText = "tetete", MediaIds = new[] { "11111", "22222" }, }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } } diff --git a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs index 84f766410..fad2f630c 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs @@ -51,7 +51,7 @@ public async Task Send_Test() SourceTweetId = new("12345"), }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } diff --git a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs index e4ebd0c5e..f8c1583e7 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs @@ -51,7 +51,7 @@ public async Task Send_Test() TweetId = new("12345"), }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } diff --git a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs index dc24d7c7d..cf623b50c 100644 --- a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs @@ -58,7 +58,7 @@ public async Task Send_Test() Count = 20, }; - var response = await request.Send(mock.Object).ConfigureAwait(false); + var response = await request.Send(mock.Object); Assert.Single(response.Tweets); Assert.Equal("DAABCgABF0HfRMjAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop); Assert.Equal("DAABCgABF0HfRMi__7QKAAIVAxUYmFWQAwgAAwAAAAIAAA", response.CursorBottom); @@ -91,7 +91,7 @@ public async Task Send_RequestCursor_Test() Cursor = "aaa", }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } } diff --git a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs index 97459b11d..f424861d8 100644 --- a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs @@ -57,7 +57,7 @@ public async Task Send_Test() Count = 20, }; - var response = await request.Send(mock.Object).ConfigureAwait(false); + var response = await request.Send(mock.Object); Assert.Single(response.Tweets); Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAABCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorTop); Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorBottom); @@ -90,7 +90,7 @@ public async Task Send_RequestCursor_Test() Cursor = "aaa", }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } } diff --git a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs index 69b7c16a7..93e857f20 100644 --- a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs @@ -56,7 +56,7 @@ public async Task Send_Test() FocalTweetId = new("1619433164757413894"), }; - var tweets = await request.Send(mock.Object).ConfigureAwait(false); + var tweets = await request.Send(mock.Object); Assert.Equal("1619433164757413894", tweets.Single().ToTwitterStatus().IdStr); mock.VerifyAll(); diff --git a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs index 77786fe8e..075ee3462 100644 --- a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs @@ -55,7 +55,7 @@ public async Task Send_Test() ScreenName = "opentween", }; - var user = await request.Send(mock.Object).ConfigureAwait(false); + var user = await request.Send(mock.Object); Assert.Equal("514241801", user.ToTwitterUser().IdStr); mock.VerifyAll(); diff --git a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs index ea1c51ed5..b829b193f 100644 --- a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs @@ -57,7 +57,7 @@ public async Task Send_Test() Count = 20, }; - var response = await request.Send(mock.Object).ConfigureAwait(false); + var response = await request.Send(mock.Object); Assert.Single(response.Tweets); Assert.Equal("DAABCgABF_tTnZvAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop); Assert.Equal("DAABCgABF_tTnZu__-0KAAIWZa6KTRoAAwgAAwAAAAIAAA", response.CursorBottom); @@ -90,7 +90,7 @@ public async Task Send_RequestCursor_Test() Cursor = "aaa", }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } } diff --git a/OpenTween.Tests/Api/ImgurApiTest.cs b/OpenTween.Tests/Api/ImgurApiTest.cs index 0114f3cb4..b675a278d 100644 --- a/OpenTween.Tests/Api/ImgurApiTest.cs +++ b/OpenTween.Tests/Api/ImgurApiTest.cs @@ -86,8 +86,7 @@ public async Task UploadFileAsync_Test() var imgurApi = new ImgurApi(ApiKey.Create("fake_api_key"), http); using var mediaItem = TestUtils.CreateDummyMediaItem(); - var uploadedUrl = await imgurApi.UploadFileAsync(mediaItem, "てすと") - .ConfigureAwait(false); + var uploadedUrl = await imgurApi.UploadFileAsync(mediaItem, "てすと"); Assert.Equal("https://i.imgur.com/aaaaaaa.png", uploadedUrl); Assert.Equal(0, mockHandler.QueueCount); diff --git a/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs b/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs index 4587f02f5..cf0d72009 100644 --- a/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs +++ b/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs @@ -60,8 +60,7 @@ public async Task TranslateAsync_Test() Assert.Equal("ja", query["to"]); Assert.Equal("en", query["from"]); - var requestBody = await x.Content.ReadAsByteArrayAsync() - .ConfigureAwait(false); + var requestBody = await x.Content.ReadAsByteArrayAsync(); using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(requestBody, XmlDictionaryReaderQuotas.Max)) { @@ -88,8 +87,7 @@ public async Task TranslateAsync_Test() }; }); - var result = await translateApi.TranslateAsync("hogehoge", langTo: "ja", langFrom: "en") - .ConfigureAwait(false); + var result = await translateApi.TranslateAsync("hogehoge", langTo: "ja", langFrom: "en"); Assert.Equal("ほげほげ", result); mock.Verify(x => x.GetAccessTokenAsync(), Times.Once()); @@ -122,8 +120,7 @@ public async Task UpdateAccessTokenIfExpired_FirstCallTest() var translateApi = mock.Object; - await translateApi.UpdateAccessTokenIfExpired() - .ConfigureAwait(false); + await translateApi.UpdateAccessTokenIfExpired(); Assert.Equal("1234abcd", translateApi.AccessToken); @@ -141,8 +138,7 @@ public async Task UpdateAccessTokenIfExpired_NotExpiredTest() translateApi.AccessToken = "1234abcd"; translateApi.RefreshAccessTokenAt = DateTimeUtc.Now + TimeSpan.FromMinutes(3); - await translateApi.UpdateAccessTokenIfExpired() - .ConfigureAwait(false); + await translateApi.UpdateAccessTokenIfExpired(); // RefreshAccessTokenAt の時刻を過ぎるまでは GetAccessTokenAsync は呼ばれない mock.Verify(x => x.GetAccessTokenAsync(), Times.Never()); @@ -159,8 +155,7 @@ public async Task UpdateAccessTokenIfExpired_ExpiredTest() translateApi.AccessToken = "1234abcd"; translateApi.RefreshAccessTokenAt = DateTimeUtc.Now - TimeSpan.FromMinutes(3); - await translateApi.UpdateAccessTokenIfExpired() - .ConfigureAwait(false); + await translateApi.UpdateAccessTokenIfExpired(); Assert.Equal("5678efgh", translateApi.AccessToken); @@ -190,8 +185,7 @@ public async Task GetAccessTokenAsync_Test() }; }); - var result = await translateApi.GetAccessTokenAsync() - .ConfigureAwait(false); + var result = await translateApi.GetAccessTokenAsync(); var expectedToken = (@"ACCESS_TOKEN", TimeSpan.FromMinutes(10)); Assert.Equal(expectedToken, result); diff --git a/OpenTween.Tests/Api/MobypictureApiTest.cs b/OpenTween.Tests/Api/MobypictureApiTest.cs index dc0899dc7..af05cb96e 100644 --- a/OpenTween.Tests/Api/MobypictureApiTest.cs +++ b/OpenTween.Tests/Api/MobypictureApiTest.cs @@ -58,8 +58,7 @@ public async Task UploadFileAsync_Test() var mobypictureApi = new MobypictureApi(ApiKey.Create("fake_api_key"), http); using var mediaItem = TestUtils.CreateDummyMediaItem(); - var uploadedUrl = await mobypictureApi.UploadFileAsync(mediaItem, "てすと") - .ConfigureAwait(false); + var uploadedUrl = await mobypictureApi.UploadFileAsync(mediaItem, "てすと"); Assert.Equal("https://www.mobypicture.com/user/OpenTween/view/00000000", uploadedUrl); Assert.Equal(0, mockHandler.QueueCount); diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 27ad41e79..49ca6b9ff 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -104,8 +104,7 @@ public async Task StatusesHomeTimeline_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.StatusesHomeTimeline(200, maxId: new("900"), sinceId: new("100")) - .ConfigureAwait(false); + await twitterApi.StatusesHomeTimeline(200, maxId: new("900"), sinceId: new("100")); mock.VerifyAll(); } @@ -133,8 +132,7 @@ public async Task StatusesMentionsTimeline_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.StatusesMentionsTimeline(200, maxId: new("900"), sinceId: new("100")) - .ConfigureAwait(false); + await twitterApi.StatusesMentionsTimeline(200, maxId: new("900"), sinceId: new("100")); mock.VerifyAll(); } @@ -164,8 +162,7 @@ public async Task StatusesUserTimeline_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.StatusesUserTimeline("twitterapi", count: 200, maxId: new("900"), sinceId: new("100")) - .ConfigureAwait(false); + await twitterApi.StatusesUserTimeline("twitterapi", count: 200, maxId: new("900"), sinceId: new("100")); mock.VerifyAll(); } @@ -191,8 +188,7 @@ public async Task StatusesShow_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.StatusesShow(statusId: new("100")) - .ConfigureAwait(false); + await twitterApi.StatusesShow(statusId: new("100")); mock.VerifyAll(); } @@ -219,8 +215,7 @@ public async Task StatusesLookup_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.StatusesLookup(statusIds: new[] { "100", "200" }) - .ConfigureAwait(false); + await twitterApi.StatusesLookup(statusIds: new[] { "100", "200" }); mock.VerifyAll(); } @@ -258,8 +253,7 @@ await twitterApi.StatusesUpdate( excludeReplyUserIds: new[] { 100L, 200L }, attachmentUrl: "https://twitter.com/twitterapi/status/22634515958" ) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -286,8 +280,7 @@ public async Task StatusesUpdate_ExcludeReplyUserIdsEmptyTest() twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, excludeReplyUserIds: Array.Empty()) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -307,8 +300,7 @@ public async Task StatusesDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesDestroy(statusId: new("100")) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -334,8 +326,7 @@ public async Task StatusesRetweet_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesRetweet(new("100")) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -366,8 +357,7 @@ public async Task SearchTweets_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.SearchTweets("from:twitterapi", "en", count: 200, maxId: new("900"), sinceId: new("100")) - .ConfigureAwait(false); + await twitterApi.SearchTweets("from:twitterapi", "en", count: 200, maxId: new("900"), sinceId: new("100")); mock.VerifyAll(); } @@ -392,8 +382,7 @@ public async Task ListsOwnerships_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsOwnerships("twitterapi", cursor: -1L, count: 100) - .ConfigureAwait(false); + await twitterApi.ListsOwnerships("twitterapi", cursor: -1L, count: 100); mock.VerifyAll(); } @@ -418,8 +407,7 @@ public async Task ListsSubscriptions_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsSubscriptions("twitterapi", cursor: -1L, count: 100) - .ConfigureAwait(false); + await twitterApi.ListsSubscriptions("twitterapi", cursor: -1L, count: 100); mock.VerifyAll(); } @@ -445,8 +433,7 @@ public async Task ListsMemberships_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsMemberships("twitterapi", cursor: -1L, count: 100, filterToOwnedLists: true) - .ConfigureAwait(false); + await twitterApi.ListsMemberships("twitterapi", cursor: -1L, count: 100, filterToOwnedLists: true); mock.VerifyAll(); } @@ -471,8 +458,7 @@ public async Task ListsCreate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -498,8 +484,7 @@ public async Task ListsUpdate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @private: true) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -522,8 +507,7 @@ public async Task ListsDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.ListsDestroy(12345L) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -553,8 +537,7 @@ public async Task ListsStatuses_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsStatuses(12345L, count: 200, maxId: new("900"), sinceId: new("100"), includeRTs: true) - .ConfigureAwait(false); + await twitterApi.ListsStatuses(12345L, count: 200, maxId: new("900"), sinceId: new("100"), includeRTs: true); mock.VerifyAll(); } @@ -581,8 +564,7 @@ public async Task ListsMembers_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsMembers(12345L, cursor: -1) - .ConfigureAwait(false); + await twitterApi.ListsMembers(12345L, cursor: -1); mock.VerifyAll(); } @@ -609,8 +591,7 @@ public async Task ListsMembersShow_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ListsMembersShow(12345L, "twitterapi") - .ConfigureAwait(false); + await twitterApi.ListsMembersShow(12345L, "twitterapi"); mock.VerifyAll(); } @@ -637,8 +618,7 @@ public async Task ListsMembersCreate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembersCreate(12345L, "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -665,8 +645,7 @@ public async Task ListsMembersDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembersDestroy(12345L, "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -690,8 +669,7 @@ public async Task DirectMessagesEventsList_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.DirectMessagesEventsList(count: 50, cursor: "12345abcdefg") - .ConfigureAwait(false); + await twitterApi.DirectMessagesEventsList(count: 50, cursor: "12345abcdefg"); mock.VerifyAll(); } @@ -731,8 +709,7 @@ public async Task DirectMessagesEventsNew_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.DirectMessagesEventsNew(recipientId: 12345L, text: "hogehoge", mediaId: 67890L) - .ConfigureAwait(false); + await twitterApi.DirectMessagesEventsNew(recipientId: 12345L, text: "hogehoge", mediaId: 67890L); mock.VerifyAll(); } @@ -750,8 +727,7 @@ public async Task DirectMessagesEventsDestroy_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.DirectMessagesEventsDestroy(eventId: new("100")) - .ConfigureAwait(false); + await twitterApi.DirectMessagesEventsDestroy(eventId: new("100")); mock.VerifyAll(); } @@ -777,8 +753,7 @@ public async Task UsersShow_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.UsersShow(screenName: "twitterapi") - .ConfigureAwait(false); + await twitterApi.UsersShow(screenName: "twitterapi"); mock.VerifyAll(); } @@ -804,8 +779,7 @@ public async Task UsersLookup_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.UsersLookup(userIds: new[] { "11111", "22222" }) - .ConfigureAwait(false); + await twitterApi.UsersLookup(userIds: new[] { "11111", "22222" }); mock.VerifyAll(); } @@ -829,8 +803,7 @@ public async Task UsersReportSpam_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.UsersReportSpam(screenName: "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -858,8 +831,7 @@ public async Task FavoritesList_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.FavoritesList(200, maxId: 900L, sinceId: 100L) - .ConfigureAwait(false); + await twitterApi.FavoritesList(200, maxId: 900L, sinceId: 100L); mock.VerifyAll(); } @@ -883,8 +855,7 @@ public async Task FavoritesCreate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.FavoritesCreate(statusId: new("100")) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -908,8 +879,7 @@ public async Task FavoritesDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.FavoritesDestroy(statusId: new("100")) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -929,8 +899,7 @@ public async Task FriendshipsShow_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.FriendshipsShow(sourceScreenName: "twitter", targetScreenName: "twitterapi") - .ConfigureAwait(false); + await twitterApi.FriendshipsShow(sourceScreenName: "twitter", targetScreenName: "twitterapi"); mock.VerifyAll(); } @@ -950,8 +919,7 @@ public async Task FriendshipsCreate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.FriendshipsCreate(screenName: "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -971,8 +939,7 @@ public async Task FriendshipsDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.FriendshipsDestroy(screenName: "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -992,8 +959,7 @@ public async Task NoRetweetIds_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.NoRetweetIds() - .ConfigureAwait(false); + await twitterApi.NoRetweetIds(); mock.VerifyAll(); } @@ -1013,8 +979,7 @@ public async Task FollowersIds_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.FollowersIds(cursor: -1L) - .ConfigureAwait(false); + await twitterApi.FollowersIds(cursor: -1L); mock.VerifyAll(); } @@ -1034,8 +999,7 @@ public async Task MutesUsersIds_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.MutesUsersIds(cursor: -1L) - .ConfigureAwait(false); + await twitterApi.MutesUsersIds(cursor: -1L); mock.VerifyAll(); } @@ -1055,8 +1019,7 @@ public async Task BlocksIds_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.BlocksIds(cursor: -1L) - .ConfigureAwait(false); + await twitterApi.BlocksIds(cursor: -1L); mock.VerifyAll(); } @@ -1080,8 +1043,7 @@ public async Task BlocksCreate_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.BlocksCreate(screenName: "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1105,8 +1067,7 @@ public async Task BlocksDestroy_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.BlocksDestroy(screenName: "twitterapi") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1135,8 +1096,7 @@ public async Task AccountVerifyCredentials_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.AccountVerifyCredentials() - .ConfigureAwait(false); + await twitterApi.AccountVerifyCredentials(); Assert.Equal(100L, twitterApi.CurrentUserId); Assert.Equal("opentween", twitterApi.CurrentScreenName); @@ -1168,8 +1128,7 @@ public async Task AccountUpdateProfile_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", location: "Location", description: "") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1197,8 +1156,7 @@ public async Task AccountUpdateProfileImage_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.AccountUpdateProfileImage(media) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1218,8 +1176,7 @@ public async Task ApplicationRateLimitStatus_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.ApplicationRateLimitStatus() - .ConfigureAwait(false); + await twitterApi.ApplicationRateLimitStatus(); mock.VerifyAll(); } @@ -1239,8 +1196,7 @@ public async Task Configuration_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.Configuration() - .ConfigureAwait(false); + await twitterApi.Configuration(); mock.VerifyAll(); } @@ -1266,8 +1222,7 @@ public async Task MediaUploadInit_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", mediaCategory: "dm_image") - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1294,8 +1249,7 @@ public async Task MediaUploadAppend_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.MediaUploadAppend(mediaId: 11111L, segmentIndex: 1, media: media) - .ConfigureAwait(false); + await twitterApi.MediaUploadAppend(mediaId: 11111L, segmentIndex: 1, media: media); mock.VerifyAll(); } @@ -1319,8 +1273,7 @@ public async Task MediaUploadFinalize_Test() twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadFinalize(mediaId: 11111L) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); mock.VerifyAll(); } @@ -1344,8 +1297,7 @@ public async Task MediaUploadStatus_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.MediaUploadStatus(mediaId: 11111L) - .ConfigureAwait(false); + await twitterApi.MediaUploadStatus(mediaId: 11111L); mock.VerifyAll(); } @@ -1364,8 +1316,7 @@ public async Task MediaMetadataCreate_Test() using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); twitterApi.ApiConnection = mock.Object; - await twitterApi.MediaMetadataCreate(mediaId: 12345L, altText: "hogehoge") - .ConfigureAwait(false); + await twitterApi.MediaMetadataCreate(mediaId: 12345L, altText: "hogehoge"); mock.VerifyAll(); } diff --git a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs index bf9900609..fcf5adfdd 100644 --- a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs @@ -57,7 +57,7 @@ public async Task StatusesMentionsTimeline_Test() UntilId = new("900"), }; - await request.Send(mock.Object).ConfigureAwait(false); + await request.Send(mock.Object); mock.VerifyAll(); } diff --git a/OpenTween.Tests/Connection/LazyJsonTest.cs b/OpenTween.Tests/Connection/LazyJsonTest.cs index 0c003aca9..6ebf63245 100644 --- a/OpenTween.Tests/Connection/LazyJsonTest.cs +++ b/OpenTween.Tests/Connection/LazyJsonTest.cs @@ -47,8 +47,7 @@ public async Task LoadJsonAsync_Test() // この時点ではまだレスポンスボディは読まれない Assert.Equal(0, bodyStream.Position); - var result = await lazyJson.LoadJsonAsync() - .ConfigureAwait(false); + var result = await lazyJson.LoadJsonAsync(); Assert.Equal("hogehoge", result); } @@ -66,8 +65,9 @@ public async Task LoadJsonAsync_InvalidJsonTest() // この時点ではまだレスポンスボディは読まれない Assert.Equal(0, bodyStream.Position); - var exception = await Assert.ThrowsAnyAsync(() => lazyJson.LoadJsonAsync()) - .ConfigureAwait(false); + var exception = await Assert.ThrowsAnyAsync( + () => lazyJson.LoadJsonAsync() + ); Assert.IsType(exception.InnerException); } @@ -86,8 +86,7 @@ public async Task IgnoreResponse_Test() // レスポンスボディを読まずに破棄 await Task.FromResult(lazyJson) - .IgnoreResponse() - .ConfigureAwait(false); + .IgnoreResponse(); Assert.True(bodyStream.IsDisposed); } diff --git a/OpenTween.Tests/Connection/OAuthHandlerTest.cs b/OpenTween.Tests/Connection/OAuthHandlerTest.cs index 51dd44dc1..db59819a9 100644 --- a/OpenTween.Tests/Connection/OAuthHandlerTest.cs +++ b/OpenTween.Tests/Connection/OAuthHandlerTest.cs @@ -36,8 +36,7 @@ public async Task GetParameter_UriQueryTest() { var requestUri = new Uri("http://example.com/api?aaa=1&bbb=2"); - var actual = await OAuthHandler.GetParameters(requestUri, content: null) - .ConfigureAwait(false); + var actual = await OAuthHandler.GetParameters(requestUri, content: null); var expected = new[] { new KeyValuePair("aaa", "1"), @@ -58,8 +57,7 @@ public async Task GetParameter_FormUrlEncodedTest() }; using var content = new FormUrlEncodedContent(formParams); - var actual = await OAuthHandler.GetParameters(requestUri, content) - .ConfigureAwait(false); + var actual = await OAuthHandler.GetParameters(requestUri, content); var expected = new[] { @@ -81,8 +79,7 @@ public async Task GetParameter_MultipartTest() content.Add(paramA, "aaa"); content.Add(paramB, "bbb"); - var actual = await OAuthHandler.GetParameters(requestUri, content) - .ConfigureAwait(false); + var actual = await OAuthHandler.GetParameters(requestUri, content); // multipart/form-data のリクエストではパラメータを署名対象にしない Assert.Empty(actual); diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index e97548196..929f45eb1 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -83,8 +83,7 @@ public async Task GetAsync_Test() ["bbbb"] = "2222", }; - var result = await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete") - .ConfigureAwait(false); + var result = await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete"); Assert.Equal("hogehoge", result); Assert.Equal(0, mockHandler.QueueCount); @@ -122,8 +121,7 @@ public async Task GetAsync_AbsoluteUriTest() ["bbbb"] = "2222", }; - await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete") - .ConfigureAwait(false); + await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete"); Assert.Equal(0, mockHandler.QueueCount); } @@ -160,8 +158,7 @@ public async Task GetAsync_UpdateRateLimitTest() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - await apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete") - .ConfigureAwait(false); + await apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete"); Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel); Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]); @@ -187,8 +184,9 @@ public async Task GetAsync_ErrorStatusTest() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var exception = await Assert.ThrowsAsync(() => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete")) - .ConfigureAwait(false); + var exception = await Assert.ThrowsAsync( + () => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete") + ); // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する Assert.Equal("BadGateway", exception.Message); @@ -215,8 +213,9 @@ public async Task GetAsync_ErrorJsonTest() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var exception = await Assert.ThrowsAsync(() => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete")) - .ConfigureAwait(false); + var exception = await Assert.ThrowsAsync( + () => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete") + ); // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する Assert.Equal("DuplicateStatus", exception.Message); @@ -260,13 +259,12 @@ public async Task GetStreamAsync_Test() ["bbbb"] = "2222", }; - var stream = await apiConnection.GetStreamAsync(endpoint, param) - .ConfigureAwait(false); + var stream = await apiConnection.GetStreamAsync(endpoint, param); using (var memoryStream = new MemoryStream()) { // 内容の比較のために MemoryStream にコピー - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + await stream.CopyToAsync(memoryStream); Assert.Equal(image.Stream.ToArray(), memoryStream.ToArray()); } @@ -288,8 +286,7 @@ public async Task PostLazyAsync_Test() Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", x.RequestUri.AbsoluteUri); - var body = await x.Content.ReadAsStringAsync() - .ConfigureAwait(false); + var body = await x.Content.ReadAsStringAsync(); var query = HttpUtility.ParseQueryString(body); Assert.Equal("1111", query["aaaa"]); @@ -308,10 +305,9 @@ public async Task PostLazyAsync_Test() ["bbbb"] = "2222", }; - var result = await apiConnection.PostLazyAsync(endpoint, param) - .ConfigureAwait(false); + var result = await apiConnection.PostLazyAsync(endpoint, param); - Assert.Equal("hogehoge", await result.LoadJsonAsync().ConfigureAwait(false)); + Assert.Equal("hogehoge", await result.LoadJsonAsync()); Assert.Equal(0, mockHandler.QueueCount); } @@ -360,7 +356,7 @@ public async Task PostLazyAsync_MultipartTest() .Concat(image.Stream.ToArray()) .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n")); - Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false)); + Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync()); return new HttpResponseMessage(HttpStatusCode.OK) { @@ -379,10 +375,9 @@ public async Task PostLazyAsync_MultipartTest() ["media1"] = media, }; - var result = await apiConnection.PostLazyAsync(endpoint, param, mediaParam) - .ConfigureAwait(false); + var result = await apiConnection.PostLazyAsync(endpoint, param, mediaParam); - Assert.Equal("hogehoge", await result.LoadJsonAsync().ConfigureAwait(false)); + Assert.Equal("hogehoge", await result.LoadJsonAsync()); Assert.Equal(0, mockHandler.QueueCount); } @@ -415,7 +410,7 @@ public async Task PostLazyAsync_Multipart_NullTest() var expected = Encoding.UTF8.GetBytes(expectedText); - Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false)); + Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync()); return new HttpResponseMessage(HttpStatusCode.OK) { @@ -425,10 +420,9 @@ public async Task PostLazyAsync_Multipart_NullTest() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var result = await apiConnection.PostLazyAsync(endpoint, param: null, media: null) - .ConfigureAwait(false); + var result = await apiConnection.PostLazyAsync(endpoint, param: null, media: null); - Assert.Equal("hogehoge", await result.LoadJsonAsync().ConfigureAwait(false)); + Assert.Equal("hogehoge", await result.LoadJsonAsync()); Assert.Equal(0, mockHandler.QueueCount); } @@ -449,8 +443,7 @@ public async Task PostJsonAsync_Test() Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString()); - var body = await x.Content.ReadAsStringAsync() - .ConfigureAwait(false); + var body = await x.Content.ReadAsStringAsync(); Assert.Equal("""{"aaaa": 1111}""", body); @@ -462,8 +455,7 @@ public async Task PostJsonAsync_Test() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}""") - .ConfigureAwait(false); + var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}"""); Assert.Equal(@"{""ok"":true}", response); Assert.Equal(0, mockHandler.QueueCount); @@ -485,8 +477,7 @@ public async Task PostJsonAsync_T_Test() Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString()); - var body = await x.Content.ReadAsStringAsync() - .ConfigureAwait(false); + var body = await x.Content.ReadAsStringAsync(); Assert.Equal("""{"aaaa": 1111}""", body); @@ -497,12 +488,8 @@ public async Task PostJsonAsync_T_Test() }); var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - - var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}""") - .ConfigureAwait(false); - - var result = await response.LoadJsonAsync() - .ConfigureAwait(false); + var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}"""); + var result = await response.LoadJsonAsync(); Assert.Equal("hogehoge", result); @@ -528,8 +515,7 @@ public async Task DeleteAsync_Test() var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - await apiConnection.DeleteAsync(endpoint) - .ConfigureAwait(false); + await apiConnection.DeleteAsync(endpoint); Assert.Equal(0, mockHandler.QueueCount); } diff --git a/OpenTween.Tests/MemoryImageTest.cs b/OpenTween.Tests/MemoryImageTest.cs index b038455c0..bf29a2f70 100644 --- a/OpenTween.Tests/MemoryImageTest.cs +++ b/OpenTween.Tests/MemoryImageTest.cs @@ -37,7 +37,7 @@ public class MemoryImageTest public async Task ImageFormat_GifTest() { using var imgStream = File.OpenRead("Resources/re.gif"); - using var image = await MemoryImage.CopyFromStreamAsync(imgStream).ConfigureAwait(false); + using var image = await MemoryImage.CopyFromStreamAsync(imgStream); Assert.Equal(ImageFormat.Gif, image.ImageFormat); Assert.Equal(".gif", image.ImageFormatExt); } @@ -58,8 +58,7 @@ public async Task CopyFromStream_Test() { using var stream = File.OpenRead("Resources/re.gif"); using var memstream = new MemoryStream(); - await stream.CopyToAsync(memstream) - .ConfigureAwait(false); + await stream.CopyToAsync(memstream); stream.Seek(0, SeekOrigin.Begin); @@ -72,13 +71,11 @@ public async Task CopyFromStreamAsync_Test() { using var stream = File.OpenRead("Resources/re.gif"); using var memstream = new MemoryStream(); - await stream.CopyToAsync(memstream) - .ConfigureAwait(false); + await stream.CopyToAsync(memstream); stream.Seek(0, SeekOrigin.Begin); - using var image = await MemoryImage.CopyFromStreamAsync(stream) - .ConfigureAwait(false); + using var image = await MemoryImage.CopyFromStreamAsync(stream); Assert.Equal(memstream.ToArray(), image.Stream.ToArray()); } @@ -87,8 +84,7 @@ public async Task CopyFromBytes_Test() { using var stream = File.OpenRead("Resources/re.gif"); using var memstream = new MemoryStream(); - await stream.CopyToAsync(memstream) - .ConfigureAwait(false); + await stream.CopyToAsync(memstream); var imageBytes = memstream.ToArray(); using var image = MemoryImage.CopyFromBytes(imageBytes); @@ -121,10 +117,10 @@ public void Dispose_Test() public async Task Equals_Test() { using var imgStream1 = File.OpenRead("Resources/re.gif"); - using var image1 = await MemoryImage.CopyFromStreamAsync(imgStream1).ConfigureAwait(false); + using var image1 = await MemoryImage.CopyFromStreamAsync(imgStream1); using var imgStream2 = File.OpenRead("Resources/re.gif"); - using var image2 = await MemoryImage.CopyFromStreamAsync(imgStream2).ConfigureAwait(false); + using var image2 = await MemoryImage.CopyFromStreamAsync(imgStream2); Assert.True(image1.Equals(image2)); Assert.True(image2.Equals(image1)); diff --git a/OpenTween.Tests/ShortcutCommandTest.cs b/OpenTween.Tests/ShortcutCommandTest.cs index f03c565e7..7e27c0d6e 100644 --- a/OpenTween.Tests/ShortcutCommandTest.cs +++ b/OpenTween.Tests/ShortcutCommandTest.cs @@ -139,7 +139,7 @@ public async Task RunCommand_Test() Assert.False(invoked); - await shortcut.RunCommand().ConfigureAwait(false); + await shortcut.RunCommand(); Assert.True(invoked); } @@ -152,13 +152,13 @@ public async Task RunCommand_AsyncTest() var shortcut = ShortcutCommand.Create(Keys.F5) .Do(async () => { - await Task.Delay(100).ConfigureAwait(false); + await Task.Delay(100); invoked = true; }); Assert.False(invoked); - await shortcut.RunCommand().ConfigureAwait(false); + await shortcut.RunCommand(); Assert.True(invoked); } diff --git a/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs b/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs index 37c92c702..982b3d2cd 100644 --- a/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs @@ -212,8 +212,7 @@ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest() var service = new FoursquareCheckin(http, ApiKey.Create("%e%INVALID_API_KEY"), ApiKey.Create("%e%INVALID_API_KEY")); var post = new PostClass(); - var thumb = await service.GetThumbnailInfoAsync("https://www.swarmapp.com/c/xxxxxxxx", post, CancellationToken.None) - .ConfigureAwait(false); + var thumb = await service.GetThumbnailInfoAsync("https://www.swarmapp.com/c/xxxxxxxx", post, CancellationToken.None); Assert.Null(thumb); } diff --git a/OpenTween.Tests/Thumbnail/Services/PbsTwimgComTest.cs b/OpenTween.Tests/Thumbnail/Services/PbsTwimgComTest.cs index 2c70e8e5e..db950584e 100644 --- a/OpenTween.Tests/Thumbnail/Services/PbsTwimgComTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/PbsTwimgComTest.cs @@ -101,8 +101,7 @@ public async Task GetThumbnailInfoAsync_ModernUrlTest() var mediaUrl = "https://pbs.twimg.com/media/DYlFv51VwAUdqWr?format=jpg&name=large"; var service = new PbsTwimgCom(); - var thumb = await service.GetThumbnailInfoAsync(mediaUrl, new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + var thumb = await service.GetThumbnailInfoAsync(mediaUrl, new PostClass(), CancellationToken.None); Assert.NotNull(thumb); Assert.Equal("https://pbs.twimg.com/media/DYlFv51VwAUdqWr?format=jpg&name=large", thumb!.ThumbnailImageUrl); @@ -115,8 +114,7 @@ public async Task GetThumbnailInfoAsync_LegacyUrlTest() var mediaUrl = "https://pbs.twimg.com/media/DYlFv51VwAUdqWr.jpg"; var service = new PbsTwimgCom(); - var thumb = await service.GetThumbnailInfoAsync(mediaUrl, new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + var thumb = await service.GetThumbnailInfoAsync(mediaUrl, new PostClass(), CancellationToken.None); Assert.NotNull(thumb); Assert.Equal("https://pbs.twimg.com/media/DYlFv51VwAUdqWr?format=jpg&name=large", thumb!.ThumbnailImageUrl); diff --git a/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs b/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs index 5a6db06fb..9d1455ea6 100644 --- a/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs @@ -102,8 +102,7 @@ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest() { var service = new Tinami(ApiKey.Create("%e%INVALID_API_KEY"), null); - var thumbinfo = await service.GetThumbnailInfoAsync("http://www.tinami.com/view/12345", new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + var thumbinfo = await service.GetThumbnailInfoAsync("http://www.tinami.com/view/12345", new PostClass(), CancellationToken.None); Assert.Null(thumbinfo); } diff --git a/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs b/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs index 3ff5aba00..45ab7a3e9 100644 --- a/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs @@ -91,8 +91,7 @@ public async Task GetThumbnailInfoAsync_RequestTest() var service = new Tumblr(ApiKey.Create("fake_api_key"), http); var url = "http://hoge.tumblr.com/post/1234567/tetetete"; - await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None); } Assert.Equal(0, handler.QueueCount); @@ -131,8 +130,7 @@ public async Task GetThumbnailInfoAsync_CustomHostnameRequestTest() // Tumblrのカスタムドメイン名を使ってるっぽいURL var url = "http://tumblr.example.com/post/1234567/tetetete"; - await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None); } Assert.Equal(0, handler.QueueCount); @@ -147,8 +145,7 @@ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest() var service = new Tumblr(ApiKey.Create("%e%INVALID_API_KEY"), http); var url = "http://hoge.tumblr.com/post/1234567/tetetete"; - var thumb = await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None) - .ConfigureAwait(false); + var thumb = await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None); Assert.Null(thumb); } } diff --git a/OpenTween.Tests/TweetThumbnailTest.cs b/OpenTween.Tests/TweetThumbnailTest.cs index 69d394913..635f8e847 100644 --- a/OpenTween.Tests/TweetThumbnailTest.cs +++ b/OpenTween.Tests/TweetThumbnailTest.cs @@ -83,8 +83,7 @@ public async Task PrepareThumbnails_Test() Media = new() { new("http://example.com/abcd") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.True(tweetThumbnail.ThumbnailAvailable); Assert.Single(tweetThumbnail.Thumbnails); @@ -108,8 +107,7 @@ public async Task PrepareThumbnails_NoThumbnailTest() Media = new() { new("http://hoge.example.com/") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.False(tweetThumbnail.ThumbnailAvailable); Assert.Throws(() => tweetThumbnail.Thumbnails); @@ -246,8 +244,7 @@ public async Task SelectedIndex_Test() Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.Equal(2, tweetThumbnail.Thumbnails.Length); Assert.Equal(0, tweetThumbnail.SelectedIndex); @@ -291,8 +288,7 @@ public async Task GetImageSearchUriGoogle_Test() Media = new() { new("http://example.com/abcd") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl); Assert.Equal( @@ -316,8 +312,7 @@ public async Task GetImageSearchUriSauceNao_Test() Media = new() { new("http://example.com/abcd") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl); Assert.Equal( @@ -341,8 +336,7 @@ public async Task Scroll_Test() Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") }, }; - await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None) - .ConfigureAwait(false); + await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None); Assert.Equal(2, tweetThumbnail.Thumbnails.Length); Assert.Equal(0, tweetThumbnail.SelectedIndex); From 864abee32da9ee6133437c5c16de5b37d3cb5c64 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 03:19:03 +0900 Subject: [PATCH 24/76] =?UTF-8?q?Task.Result=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=20(xUnit1031)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://xunit.net/xunit.analyzers/rules/xUnit1031 --- OpenTween.Tests/AsyncTimerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenTween.Tests/AsyncTimerTest.cs b/OpenTween.Tests/AsyncTimerTest.cs index c3f1a785a..d897fe18a 100644 --- a/OpenTween.Tests/AsyncTimerTest.cs +++ b/OpenTween.Tests/AsyncTimerTest.cs @@ -86,7 +86,7 @@ void Handler(object sender, ThreadExceptionEventArgs ev) var timeout = Task.Delay(1000); Assert.NotEqual(timeout, await Task.WhenAny(tcs.Task, timeout)); - Assert.IsType(tcs.Task.Result); + Assert.IsType(await tcs.Task); } finally { From a4a2838c8b53947f0ac6b1e558a7f36b4eac1b6f Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 03:26:24 +0900 Subject: [PATCH 25/76] =?UTF-8?q?Assert.Empty,=20Assert.Single=20=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=20(xUnit2013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://xunit.net/xunit.analyzers/rules/xUnit2013 --- OpenTween.Tests/Models/TabInformationTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenTween.Tests/Models/TabInformationTest.cs b/OpenTween.Tests/Models/TabInformationTest.cs index 25de25bde..56ee16757 100644 --- a/OpenTween.Tests/Models/TabInformationTest.cs +++ b/OpenTween.Tests/Models/TabInformationTest.cs @@ -327,7 +327,7 @@ public void LoadTabsFromSettings_Test() }; var tabinfo = this.CreateInstance(); tabinfo.LoadTabsFromSettings(settingTabs); - Assert.Equal(1, tabinfo.Tabs.Count); + Assert.Single(tabinfo.Tabs); var tab = (PublicSearchTabModel)tabinfo.Tabs["hoge"]; Assert.Equal("aaa", tab.SearchWords); @@ -430,7 +430,7 @@ public void CreateTabFromSettings_PublicSearchTabTest() public void AddDefaultTabs_Test() { var tabinfo = this.CreateInstance(); - Assert.Equal(0, tabinfo.Tabs.Count); + Assert.Empty(tabinfo.Tabs); tabinfo.AddDefaultTabs(); From c88d796b54cd821ea0a2698a7c71834e474c1069 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 03:28:15 +0900 Subject: [PATCH 26/76] =?UTF-8?q?MemberData=E3=81=A8=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E5=BC=95=E6=95=B0=E3=81=AE=E5=9E=8B=E3=82=92?= =?UTF-8?q?=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B=20(xUnit1012,=20xUnit1039)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://xunit.net/xunit.analyzers/rules/xUnit1012 https://xunit.net/xunit.analyzers/rules/xUnit1039 --- OpenTween.Tests/MyCommonTest.cs | 2 +- OpenTween.Tests/Thumbnail/Services/YoutubeTest.cs | 2 +- OpenTween.Tests/Thumbnail/ThumbnailGeneratorTest.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenTween.Tests/MyCommonTest.cs b/OpenTween.Tests/MyCommonTest.cs index 71ffbf50b..560c6a82b 100644 --- a/OpenTween.Tests/MyCommonTest.cs +++ b/OpenTween.Tests/MyCommonTest.cs @@ -346,7 +346,7 @@ public void CreateBrowserProcessStartInfo_QuotedBrowserPathWithArgsTest() Assert.False(startInfo.UseShellExecute); } - public static readonly TheoryData ToRangeChunkTestCase = new() + public static readonly TheoryData ToRangeChunkTestCase = new() { { new[] { 1 }, diff --git a/OpenTween.Tests/Thumbnail/Services/YoutubeTest.cs b/OpenTween.Tests/Thumbnail/Services/YoutubeTest.cs index 4d4b481bb..1d3679cc0 100644 --- a/OpenTween.Tests/Thumbnail/Services/YoutubeTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/YoutubeTest.cs @@ -42,7 +42,7 @@ public class YoutubeTest [InlineData("https://youtu.be/aaaaa", "aaaaa")] [InlineData("https://youtu.be/aaaaa?t=123", "aaaaa")] [InlineData("https://www.youtube.com/channel/aaaaa", null)] // チャンネルページ - public void UrlPatternRegex_Test(string testUrl, string expected) + public void UrlPatternRegex_Test(string testUrl, string? expected) { var match = Youtube.UrlPatternRegex.Match(testUrl); diff --git a/OpenTween.Tests/Thumbnail/ThumbnailGeneratorTest.cs b/OpenTween.Tests/Thumbnail/ThumbnailGeneratorTest.cs index 0c61bd71f..eb513712d 100644 --- a/OpenTween.Tests/Thumbnail/ThumbnailGeneratorTest.cs +++ b/OpenTween.Tests/Thumbnail/ThumbnailGeneratorTest.cs @@ -39,7 +39,7 @@ public class ThumbnailGeneratorTest [InlineData("https://www.instagram.com/hogehoge/p/aaaaaaaaaaa/", "aaaaaaaaaaa")] // ユーザー名付き [InlineData("https://www.instagram.com/p/aaaaaaaaaaa/?utm_medium=copy_link", "aaaaaaaaaaa")] // トラッキングパラメータ付き [InlineData("https://www.instagram.com/hogehoge/", null)] // プロフィールページ - public void InstagramPattern_IsMatchTest(string testUrl, string expected) + public void InstagramPattern_IsMatchTest(string testUrl, string? expected) { var match = ThumbnailGenerator.InstagramPattern.Match(testUrl); From c26b6c42cf6ad62fa7a8d71033cbc40f4b07a9d9 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 03:50:12 +0900 Subject: [PATCH 27/76] =?UTF-8?q?LRUCacheDictionary=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=ABAssert.Contains=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ICollection>.Contains() の実装をテストすることができないため --- OpenTween.Tests/LRUCacheDictionaryTest.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/OpenTween.Tests/LRUCacheDictionaryTest.cs b/OpenTween.Tests/LRUCacheDictionaryTest.cs index d9d56bd39..b921fcea3 100644 --- a/OpenTween.Tests/LRUCacheDictionaryTest.cs +++ b/OpenTween.Tests/LRUCacheDictionaryTest.cs @@ -223,10 +223,12 @@ public void ContainsTest() ["key3"] = "value3", }; - Assert.Contains(new KeyValuePair("key1", "value1"), dict); - Assert.DoesNotContain(new KeyValuePair("key3", "value2"), dict); - Assert.DoesNotContain(new KeyValuePair("value3", "key3"), dict); - Assert.DoesNotContain(new KeyValuePair("hogehoge", "hogehoge"), dict); +#pragma warning disable xUnit2017 + Assert.True(dict.Contains(new("key1", "value1"))); + Assert.False(dict.Contains(new("key3", "value2"))); + Assert.False(dict.Contains(new("value3", "key3"))); + Assert.False(dict.Contains(new("hogehoge", "hogehoge"))); +#pragma warning restore xUnit2017 } [Fact] From d0d78f3cd4510dbadf06e6e3a23f63dbe816a4d4 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 02:55:30 +0900 Subject: [PATCH 28/76] =?UTF-8?q?Moq=204.20.70=20=E3=81=AB=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs | 6 +++--- OpenTween.Tests/OpenTween.Tests.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs b/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs index cf0d72009..47403f130 100644 --- a/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs +++ b/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs @@ -114,7 +114,7 @@ await Assert.ThrowsAsync( [Fact] public async Task UpdateAccessTokenIfExpired_FirstCallTest() { - var mock = new Mock(ApiKey.Create("fake_api_key"), null); + var mock = new Mock(ApiKey.Create("fake_api_key"), null!); mock.Setup(x => x.GetAccessTokenAsync()) .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000))); @@ -132,7 +132,7 @@ public async Task UpdateAccessTokenIfExpired_FirstCallTest() [Fact] public async Task UpdateAccessTokenIfExpired_NotExpiredTest() { - var mock = new Mock(ApiKey.Create("fake_api_key"), null); + var mock = new Mock(ApiKey.Create("fake_api_key"), null!); var translateApi = mock.Object; translateApi.AccessToken = "1234abcd"; @@ -147,7 +147,7 @@ public async Task UpdateAccessTokenIfExpired_NotExpiredTest() [Fact] public async Task UpdateAccessTokenIfExpired_ExpiredTest() { - var mock = new Mock(ApiKey.Create("fake_api_key"), null); + var mock = new Mock(ApiKey.Create("fake_api_key"), null!); mock.Setup(x => x.GetAccessTokenAsync()) .ReturnsAsync(("5678efgh", TimeSpan.FromSeconds(1000))); diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index 77ed422b6..1f8a54fd6 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 953a4cfc1c98d5a67afcc1e224077486eed2a323 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 02:57:13 +0900 Subject: [PATCH 29/76] =?UTF-8?q?AltCover=208.6.95=20=E3=81=AB=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- OpenTween.Tests/OpenTween.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed50570db..840a568af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: - name: Run tests shell: pwsh run: | - $altCoverVersion = '8.6.61' + $altCoverVersion = '8.6.95' $xunitVersion = '2.6.2' $targetFramework = 'net48' $altCoverPath = "$($env:NUGET_PACKAGES)\altcover\$($altCoverVersion)\tools\net472\AltCover.exe" diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index 1f8a54fd6..70754bdeb 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -26,7 +26,7 @@ - + all From 90ba566d57adc4c31f8aa3f0009ae3f07bc781d0 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 22:53:48 +0900 Subject: [PATCH 30/76] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=82=AB?= =?UTF-8?q?=E3=83=90=E3=83=AC=E3=83=83=E3=82=B8=E3=81=AE=E9=80=81=E4=BF=A1?= =?UTF-8?q?=E3=81=ABCodecov=E5=85=AC=E5=BC=8F=E3=81=AEworkflow=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 840a568af..dcfad8c23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,8 +96,7 @@ jobs: exit $p.ExitCode } - - name: Upload test results to codecov - shell: pwsh - run: | - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - .\codecov.exe -f coverage.xml + - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true From 76bb5694513d45d113cbc36bcfafdd12405cf52b Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 7 Dec 2023 23:01:47 +0900 Subject: [PATCH 31/76] =?UTF-8?q?TweenMain.Dispose=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E3=83=AA=E3=82=B9=E3=83=8A?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E8=A7=A3=E9=99=A4=E6=BC=8F=E3=82=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Tween.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 5e0a527ab..efadf0c97 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -598,6 +598,7 @@ protected override void Dispose(bool disposing) // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx Microsoft.Win32.SystemEvents.PowerModeChanged -= this.SystemEvents_PowerModeChanged; Microsoft.Win32.SystemEvents.TimeChanged -= this.SystemEvents_TimeChanged; + MyCommon.TwitterApiInfo.AccessLimitUpdated -= this.TwitterApiStatus_AccessLimitUpdated; this.disposed = true; } From 3f1155acce5c3f2e64fa95b2addea128d9f2a2a1 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 8 Dec 2023 22:41:01 +0900 Subject: [PATCH 32/76] =?UTF-8?q?IApiConnection=E3=82=92IApiConnectionLega?= =?UTF-8?q?cy=E3=81=AB=E5=90=8D=E5=89=8D=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/CreateRetweetRequestTest.cs | 2 +- .../Api/GraphQL/CreateTweetRequestTest.cs | 6 +- .../Api/GraphQL/DeleteRetweetRequestTest.cs | 2 +- .../Api/GraphQL/DeleteTweetRequestTest.cs | 2 +- .../ListLatestTweetsTimelineRequestTest.cs | 4 +- .../Api/GraphQL/SearchTimelineRequestTest.cs | 4 +- .../Api/GraphQL/TweetDetailRequestTest.cs | 2 +- .../GraphQL/UserByScreenNameRequestTest.cs | 4 +- .../UserTweetsAndRepliesRequestTest.cs | 4 +- OpenTween.Tests/Api/TwitterApiTest.cs | 98 +++++++++---------- .../Api/TwitterV2/GetTimelineRequestTest.cs | 2 +- OpenTween/Api/GraphQL/CreateRetweetRequest.cs | 2 +- OpenTween/Api/GraphQL/CreateTweetRequest.cs | 2 +- OpenTween/Api/GraphQL/DeleteRetweetRequest.cs | 2 +- OpenTween/Api/GraphQL/DeleteTweetRequest.cs | 2 +- .../ListLatestTweetsTimelineRequest.cs | 2 +- .../Api/GraphQL/SearchTimelineRequest.cs | 2 +- OpenTween/Api/GraphQL/TweetDetailRequest.cs | 2 +- .../Api/GraphQL/UserByScreenNameRequest.cs | 2 +- .../GraphQL/UserTweetsAndRepliesRequest.cs | 2 +- OpenTween/Api/TwitterApi.cs | 4 +- OpenTween/Api/TwitterV2/GetTimelineRequest.cs | 2 +- ...iConnection.cs => IApiConnectionLegacy.cs} | 2 +- OpenTween/Connection/TwitterApiConnection.cs | 2 +- OpenTween/Thumbnail/Services/TonTwitterCom.cs | 2 +- 25 files changed, 80 insertions(+), 80 deletions(-) rename OpenTween/Connection/{IApiConnection.cs => IApiConnectionLegacy.cs} (97%) diff --git a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs index 418d38f7f..5fee38558 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs @@ -38,7 +38,7 @@ public async Task Send_Test() { var responseText = File.ReadAllText("Resources/Responses/CreateRetweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs index ef5d8e85d..ad08aec06 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs @@ -38,7 +38,7 @@ public async Task Send_Test() { var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) @@ -67,7 +67,7 @@ public async Task Send_ReplyTest() { var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) @@ -92,7 +92,7 @@ public async Task Send_MediaTest() { var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs index fad2f630c..9a52489d3 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs @@ -36,7 +36,7 @@ public class DeleteRetweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs index f8c1583e7..3db62ccec 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs @@ -36,7 +36,7 @@ public class DeleteTweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync(It.IsAny(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs index cf623b50c..1a5965914 100644 --- a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs @@ -39,7 +39,7 @@ public async Task Send_Test() { using var responseStream = File.OpenRead("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) @@ -71,7 +71,7 @@ public async Task Send_RequestCursor_Test() { using var responseStream = File.OpenRead("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs index f424861d8..426df2741 100644 --- a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs @@ -38,7 +38,7 @@ public async Task Send_Test() { using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) @@ -70,7 +70,7 @@ public async Task Send_RequestCursor_Test() { using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs index 93e857f20..44aebf572 100644 --- a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs @@ -39,7 +39,7 @@ public async Task Send_Test() { using var responseStream = File.OpenRead("Resources/Responses/TweetDetail.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs index 075ee3462..9f8ee7dff 100644 --- a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs @@ -38,7 +38,7 @@ public async Task Send_Test() { using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) @@ -66,7 +66,7 @@ public async Task Send_UserUnavailableTest() { using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName_Suspended.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs index b829b193f..690cd4471 100644 --- a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs @@ -38,7 +38,7 @@ public async Task Send_Test() { using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) @@ -70,7 +70,7 @@ public async Task Send_RequestCursor_Test() { using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) ) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 49ca6b9ff..d74088b2f 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -84,7 +84,7 @@ public void Initialize_Test() [Fact] public async Task StatusesHomeTimeline_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("statuses/home_timeline.json", UriKind.Relative), @@ -112,7 +112,7 @@ public async Task StatusesHomeTimeline_Test() [Fact] public async Task StatusesMentionsTimeline_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("statuses/mentions_timeline.json", UriKind.Relative), @@ -140,7 +140,7 @@ public async Task StatusesMentionsTimeline_Test() [Fact] public async Task StatusesUserTimeline_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("statuses/user_timeline.json", UriKind.Relative), @@ -170,7 +170,7 @@ public async Task StatusesUserTimeline_Test() [Fact] public async Task StatusesShow_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("statuses/show.json", UriKind.Relative), @@ -196,7 +196,7 @@ public async Task StatusesShow_Test() [Fact] public async Task StatusesLookup_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("statuses/lookup.json", UriKind.Relative), @@ -223,7 +223,7 @@ public async Task StatusesLookup_Test() [Fact] public async Task StatusesUpdate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("statuses/update.json", UriKind.Relative), @@ -261,7 +261,7 @@ await twitterApi.StatusesUpdate( [Fact] public async Task StatusesUpdate_ExcludeReplyUserIdsEmptyTest() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("statuses/update.json", UriKind.Relative), @@ -288,7 +288,7 @@ await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, exc [Fact] public async Task StatusesDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("statuses/destroy.json", UriKind.Relative), @@ -308,7 +308,7 @@ public async Task StatusesDestroy_Test() [Fact] public async Task StatusesRetweet_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("statuses/retweet.json", UriKind.Relative), @@ -334,7 +334,7 @@ await twitterApi.StatusesRetweet(new("100")) [Fact] public async Task SearchTweets_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("search/tweets.json", UriKind.Relative), @@ -365,7 +365,7 @@ public async Task SearchTweets_Test() [Fact] public async Task ListsOwnerships_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/ownerships.json", UriKind.Relative), @@ -390,7 +390,7 @@ public async Task ListsOwnerships_Test() [Fact] public async Task ListsSubscriptions_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/subscriptions.json", UriKind.Relative), @@ -415,7 +415,7 @@ public async Task ListsSubscriptions_Test() [Fact] public async Task ListsMemberships_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/memberships.json", UriKind.Relative), @@ -441,7 +441,7 @@ public async Task ListsMemberships_Test() [Fact] public async Task ListsCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("lists/create.json", UriKind.Relative), @@ -466,7 +466,7 @@ await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true) [Fact] public async Task ListsUpdate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("lists/update.json", UriKind.Relative), @@ -492,7 +492,7 @@ await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @pri [Fact] public async Task ListsDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("lists/destroy.json", UriKind.Relative), @@ -515,7 +515,7 @@ await twitterApi.ListsDestroy(12345L) [Fact] public async Task ListsStatuses_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/statuses.json", UriKind.Relative), @@ -545,7 +545,7 @@ public async Task ListsStatuses_Test() [Fact] public async Task ListsMembers_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/members.json", UriKind.Relative), @@ -572,7 +572,7 @@ public async Task ListsMembers_Test() [Fact] public async Task ListsMembersShow_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("lists/members/show.json", UriKind.Relative), @@ -599,7 +599,7 @@ public async Task ListsMembersShow_Test() [Fact] public async Task ListsMembersCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("lists/members/create.json", UriKind.Relative), @@ -626,7 +626,7 @@ await twitterApi.ListsMembersCreate(12345L, "twitterapi") [Fact] public async Task ListsMembersDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("lists/members/destroy.json", UriKind.Relative), @@ -653,7 +653,7 @@ await twitterApi.ListsMembersDestroy(12345L, "twitterapi") [Fact] public async Task DirectMessagesEventsList_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("direct_messages/events/list.json", UriKind.Relative), @@ -677,7 +677,7 @@ public async Task DirectMessagesEventsList_Test() [Fact] public async Task DirectMessagesEventsNew_Test() { - var mock = new Mock(); + var mock = new Mock(); var responseText = """ { "event": { @@ -717,7 +717,7 @@ public async Task DirectMessagesEventsNew_Test() [Fact] public async Task DirectMessagesEventsDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.DeleteAsync( new Uri("direct_messages/events/destroy.json?id=100", UriKind.Relative)) @@ -735,7 +735,7 @@ public async Task DirectMessagesEventsDestroy_Test() [Fact] public async Task UsersShow_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("users/show.json", UriKind.Relative), @@ -761,7 +761,7 @@ public async Task UsersShow_Test() [Fact] public async Task UsersLookup_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("users/lookup.json", UriKind.Relative), @@ -787,7 +787,7 @@ public async Task UsersLookup_Test() [Fact] public async Task UsersReportSpam_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("users/report_spam.json", UriKind.Relative), @@ -811,7 +811,7 @@ await twitterApi.UsersReportSpam(screenName: "twitterapi") [Fact] public async Task FavoritesList_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("favorites/list.json", UriKind.Relative), @@ -839,7 +839,7 @@ public async Task FavoritesList_Test() [Fact] public async Task FavoritesCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("favorites/create.json", UriKind.Relative), @@ -863,7 +863,7 @@ public async Task FavoritesCreate_Test() [Fact] public async Task FavoritesDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("favorites/destroy.json", UriKind.Relative), @@ -887,7 +887,7 @@ public async Task FavoritesDestroy_Test() [Fact] public async Task FriendshipsShow_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("friendships/show.json", UriKind.Relative), @@ -907,7 +907,7 @@ public async Task FriendshipsShow_Test() [Fact] public async Task FriendshipsCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("friendships/create.json", UriKind.Relative), @@ -927,7 +927,7 @@ await twitterApi.FriendshipsCreate(screenName: "twitterapi") [Fact] public async Task FriendshipsDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("friendships/destroy.json", UriKind.Relative), @@ -947,7 +947,7 @@ await twitterApi.FriendshipsDestroy(screenName: "twitterapi") [Fact] public async Task NoRetweetIds_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("friendships/no_retweets/ids.json", UriKind.Relative), @@ -967,7 +967,7 @@ public async Task NoRetweetIds_Test() [Fact] public async Task FollowersIds_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("followers/ids.json", UriKind.Relative), @@ -987,7 +987,7 @@ public async Task FollowersIds_Test() [Fact] public async Task MutesUsersIds_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("mutes/users/ids.json", UriKind.Relative), @@ -1007,7 +1007,7 @@ public async Task MutesUsersIds_Test() [Fact] public async Task BlocksIds_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("blocks/ids.json", UriKind.Relative), @@ -1027,7 +1027,7 @@ public async Task BlocksIds_Test() [Fact] public async Task BlocksCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("blocks/create.json", UriKind.Relative), @@ -1051,7 +1051,7 @@ await twitterApi.BlocksCreate(screenName: "twitterapi") [Fact] public async Task BlocksDestroy_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("blocks/destroy.json", UriKind.Relative), @@ -1075,7 +1075,7 @@ await twitterApi.BlocksDestroy(screenName: "twitterapi") [Fact] public async Task AccountVerifyCredentials_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("account/verify_credentials.json", UriKind.Relative), @@ -1107,7 +1107,7 @@ public async Task AccountVerifyCredentials_Test() [Fact] public async Task AccountUpdateProfile_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("account/update_profile.json", UriKind.Relative), @@ -1138,7 +1138,7 @@ public async Task AccountUpdateProfileImage_Test() { using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("account/update_profile_image.json", UriKind.Relative), @@ -1164,7 +1164,7 @@ await twitterApi.AccountUpdateProfileImage(media) [Fact] public async Task ApplicationRateLimitStatus_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("application/rate_limit_status.json", UriKind.Relative), @@ -1184,7 +1184,7 @@ public async Task ApplicationRateLimitStatus_Test() [Fact] public async Task Configuration_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("help/configuration.json", UriKind.Relative), @@ -1204,7 +1204,7 @@ public async Task Configuration_Test() [Fact] public async Task MediaUploadInit_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), @@ -1232,7 +1232,7 @@ public async Task MediaUploadAppend_Test() { using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostAsync( new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), @@ -1257,7 +1257,7 @@ public async Task MediaUploadAppend_Test() [Fact] public async Task MediaUploadFinalize_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostLazyAsync( new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), @@ -1281,7 +1281,7 @@ await twitterApi.MediaUploadFinalize(mediaId: 11111L) [Fact] public async Task MediaUploadStatus_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), @@ -1305,7 +1305,7 @@ public async Task MediaUploadStatus_Test() [Fact] public async Task MediaMetadataCreate_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.PostJsonAsync( new Uri("https://upload.twitter.com/1.1/media/metadata/create.json", UriKind.Absolute), diff --git a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs index fcf5adfdd..a2b00fd96 100644 --- a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs @@ -34,7 +34,7 @@ public class GetTimelineRequestTest [Fact] public async Task StatusesMentionsTimeline_Test() { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.GetAsync( new Uri("/2/users/100/timelines/reverse_chronological", UriKind.Relative), diff --git a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs index 33c579df2..35b5060e2 100644 --- a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs @@ -49,7 +49,7 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var json = this.CreateRequestBody(); var response = await apiConnection.PostJsonAsync(EndpointUri, json); diff --git a/OpenTween/Api/GraphQL/CreateTweetRequest.cs b/OpenTween/Api/GraphQL/CreateTweetRequest.cs index d2b0f229c..46e1075ac 100644 --- a/OpenTween/Api/GraphQL/CreateTweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateTweetRequest.cs @@ -153,7 +153,7 @@ public string CreateRequestBody() return JsonUtils.SerializeJsonByDataContract(body); } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var json = this.CreateRequestBody(); var response = await apiConnection.PostJsonAsync(EndpointUri, json); diff --git a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs index ab5b0aedc..00a0b8368 100644 --- a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs @@ -45,7 +45,7 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var json = this.CreateRequestBody(); var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); diff --git a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs index feeee14eb..adccacec0 100644 --- a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs @@ -45,7 +45,7 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var json = this.CreateRequestBody(); var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); diff --git a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs index a241e4a86..fc1326be4 100644 --- a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs @@ -84,7 +84,7 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var param = this.CreateParameters(); diff --git a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs index 9e76ca05b..e157a2903 100644 --- a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs @@ -86,7 +86,7 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var param = this.CreateParameters(); diff --git a/OpenTween/Api/GraphQL/TweetDetailRequest.cs b/OpenTween/Api/GraphQL/TweetDetailRequest.cs index 496e2f47c..0bf750cce 100644 --- a/OpenTween/Api/GraphQL/TweetDetailRequest.cs +++ b/OpenTween/Api/GraphQL/TweetDetailRequest.cs @@ -60,7 +60,7 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var param = this.CreateParameters(); diff --git a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs index 8d5607ef5..b4c04faf1 100644 --- a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs +++ b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs @@ -59,7 +59,7 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var param = this.CreateParameters(); diff --git a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs index 916a914b8..3fad57847 100644 --- a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs +++ b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs @@ -69,7 +69,7 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnection apiConnection) + public async Task Send(IApiConnectionLegacy apiConnection) { var param = this.CreateParameters(); diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 1f3b68e63..abce76331 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -41,9 +41,9 @@ public sealed class TwitterApi : IDisposable public string CurrentScreenName { get; private set; } = ""; - public IApiConnection Connection => this.ApiConnection ?? throw new InvalidOperationException(); + public IApiConnectionLegacy Connection => this.ApiConnection ?? throw new InvalidOperationException(); - internal IApiConnection? ApiConnection; + internal IApiConnectionLegacy? ApiConnection; public TwitterAppToken AppToken { get; private set; } = TwitterAppToken.GetDefault(); diff --git a/OpenTween/Api/TwitterV2/GetTimelineRequest.cs b/OpenTween/Api/TwitterV2/GetTimelineRequest.cs index 6ff98cf55..677d167ae 100644 --- a/OpenTween/Api/TwitterV2/GetTimelineRequest.cs +++ b/OpenTween/Api/TwitterV2/GetTimelineRequest.cs @@ -69,7 +69,7 @@ private Dictionary CreateParameters() return param; } - public Task Send(IApiConnection apiConnection) + public Task Send(IApiConnectionLegacy apiConnection) { var uri = this.CreateEndpointUri(); var param = this.CreateParameters(); diff --git a/OpenTween/Connection/IApiConnection.cs b/OpenTween/Connection/IApiConnectionLegacy.cs similarity index 97% rename from OpenTween/Connection/IApiConnection.cs rename to OpenTween/Connection/IApiConnectionLegacy.cs index b7c866f4c..9e2231840 100644 --- a/OpenTween/Connection/IApiConnection.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -30,7 +30,7 @@ namespace OpenTween.Connection { - public interface IApiConnection : IDisposable + public interface IApiConnectionLegacy : IDisposable { Task GetAsync(Uri uri, IDictionary? param, string? endpointName); diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index b8bd085e1..338c8358a 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -39,7 +39,7 @@ namespace OpenTween.Connection { - public class TwitterApiConnection : IApiConnection, IDisposable + public class TwitterApiConnection : IApiConnectionLegacy, IDisposable { public static Uri RestApiBase { get; set; } = new("https://api.twitter.com/1.1/"); diff --git a/OpenTween/Thumbnail/Services/TonTwitterCom.cs b/OpenTween/Thumbnail/Services/TonTwitterCom.cs index 80fe74fe3..b42844852 100644 --- a/OpenTween/Thumbnail/Services/TonTwitterCom.cs +++ b/OpenTween/Thumbnail/Services/TonTwitterCom.cs @@ -40,7 +40,7 @@ namespace OpenTween.Thumbnail.Services /// public class TonTwitterCom : IThumbnailService { - internal static Func? GetApiConnection; + internal static Func? GetApiConnection; public override Task GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token) { From 143acec4822a0b8b6d55e763da2252b4c70f154e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 8 Dec 2023 23:57:30 +0900 Subject: [PATCH 33/76] =?UTF-8?q?IApiConnection,=20IHttpRequest,=20ApiResp?= =?UTF-8?q?onse=E3=81=A7=E6=A7=8B=E6=88=90=E3=81=99=E3=82=8B=E6=96=B0?= =?UTF-8?q?=E3=81=97=E3=81=84TwitterApiConnection=E3=82=92=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Connection/ApiResponseTest.cs | 119 ++++++++++++++ OpenTween.Tests/Connection/GetRequestTest.cs | 81 ++++++++++ .../Connection/TwitterApiConnectionTest.cs | 151 ++++++++++++++++-- OpenTween.Tests/MyCommonTest.cs | 10 +- OpenTween/Connection/ApiResponse.cs | 99 ++++++++++++ OpenTween/Connection/GetRequest.cs | 56 +++++++ OpenTween/Connection/IApiConnection.cs | 33 ++++ OpenTween/Connection/IApiConnectionLegacy.cs | 2 +- OpenTween/Connection/IHttpRequest.cs | 35 ++++ OpenTween/Connection/TwitterApiConnection.cs | 90 ++++++++--- OpenTween/MyCommon.cs | 6 +- 11 files changed, 642 insertions(+), 40 deletions(-) create mode 100644 OpenTween.Tests/Connection/ApiResponseTest.cs create mode 100644 OpenTween.Tests/Connection/GetRequestTest.cs create mode 100644 OpenTween/Connection/ApiResponse.cs create mode 100644 OpenTween/Connection/GetRequest.cs create mode 100644 OpenTween/Connection/IApiConnection.cs create mode 100644 OpenTween/Connection/IHttpRequest.cs diff --git a/OpenTween.Tests/Connection/ApiResponseTest.cs b/OpenTween.Tests/Connection/ApiResponseTest.cs new file mode 100644 index 000000000..a1ff5eee1 --- /dev/null +++ b/OpenTween.Tests/Connection/ApiResponseTest.cs @@ -0,0 +1,119 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System.Net; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml.Linq; +using OpenTween.Api; +using Xunit; + +namespace OpenTween.Connection +{ + public class ApiResponseTest + { + [Fact] + public async Task ReadAsBytes_Test() + { + using var responseContent = new ByteArrayContent(new byte[] { 1, 2, 3 }); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + Assert.Equal(new byte[] { 1, 2, 3 }, await response.ReadAsBytes()); + } + + [DataContract] + public struct TestJson + { + [DataMember(Name = "foo")] + public int Foo { get; set; } + } + + [Fact] + public async Task ReadAsJson_Test() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + Assert.Equal(new() { Foo = 123 }, await response.ReadAsJson()); + } + + [Fact] + public async Task ReadAsJson_InvalidJsonTest() + { + using var responseContent = new StringContent("### Invalid JSON Response ###"); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + var ex = await Assert.ThrowsAsync( + () => response.ReadAsJson() + ); + Assert.Equal("### Invalid JSON Response ###", ex.ResponseText); + } + + [Fact] + public async Task ReadAsJsonXml_Test() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + var rootElm = await response.ReadAsJsonXml(); + var xmlString = rootElm.ToString(SaveOptions.DisableFormatting); + Assert.Equal("""123""", xmlString); + } + + [Fact] + public async Task ReadAsJsonXml_InvalidJsonTest() + { + using var responseContent = new StringContent("### Invalid JSON Response ###"); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + var ex = await Assert.ThrowsAsync( + () => response.ReadAsJsonXml() + ); + Assert.Equal("### Invalid JSON Response ###", ex.ResponseText); + } + } +} diff --git a/OpenTween.Tests/Connection/GetRequestTest.cs b/OpenTween.Tests/Connection/GetRequestTest.cs new file mode 100644 index 000000000..db738b66f --- /dev/null +++ b/OpenTween.Tests/Connection/GetRequestTest.cs @@ -0,0 +1,81 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; + +namespace OpenTween.Connection +{ + public class GetRequestTest + { + [Fact] + public void CreateMessage_Test() + { + var request = new GetRequest + { + RequestUri = new("statuses/show.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = "12345", + }, + }; + + var baseUri = new Uri("https://api.twitter.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Get, requestMessage.Method); + Assert.Equal(new("https://api.twitter.com/v1/statuses/show.json?id=12345"), requestMessage.RequestUri); + } + + [Fact] + public void BuildUriWithQuery_Test() + { + var uri = new Uri("https://example.com/hoge"); + var query = new Dictionary + { + ["foo"] = "bar", + }; + Assert.Equal(new("https://example.com/hoge?foo=bar"), GetRequest.BuildUriWithQuery(uri, query)); + } + + [Fact] + public void BuildUriWithQuery_NullTest() + { + var uri = new Uri("https://example.com/hoge"); + Assert.Equal(new("https://example.com/hoge"), GetRequest.BuildUriWithQuery(uri, null)); + } + + [Fact] + public void BuildUriWithQuery_CannotMergeTest() + { + var uri = new Uri("https://example.com/hoge?aaa=111"); + var query = new Dictionary + { + ["bbb"] = "222", + }; + Assert.Throws( + () => GetRequest.BuildUriWithQuery(uri, query) + ); + } + } +} diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index 929f45eb1..1c7b53cdf 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -29,6 +29,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Web; using Moq; @@ -127,7 +128,50 @@ public async Task GetAsync_AbsoluteUriTest() } [Fact] - public async Task GetAsync_UpdateRateLimitTest() + public async Task SendAsync_Test() + { + using var mockHandler = new HttpMessageHandlerMock(); + using var http = new HttpClient(mockHandler); + using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + apiConnection.Http = http; + + mockHandler.Enqueue(x => + { + Assert.Equal(HttpMethod.Get, x.Method); + Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", + x.RequestUri.GetLeftPart(UriPartial.Path)); + + var query = HttpUtility.ParseQueryString(x.RequestUri.Query); + + Assert.Equal("1111", query["aaaa"]); + Assert.Equal("2222", query["bbbb"]); + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("\"hogehoge\""), + }; + }); + + var request = new GetRequest + { + RequestUri = new("hoge/tetete.json", UriKind.Relative), + Query = new Dictionary + { + ["aaaa"] = "1111", + ["bbbb"] = "2222", + }, + EndpointName = "/hoge/tetete", + }; + + using var response = await apiConnection.SendAsync(request); + + Assert.Equal("hogehoge", await response.ReadAsJson()); + + Assert.Equal(0, mockHandler.QueueCount); + } + + [Fact] + public async Task SendAsync_UpdateRateLimitTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); @@ -144,10 +188,10 @@ public async Task GetAsync_UpdateRateLimitTest() { Headers = { - { "X-Rate-Limit-Limit", "150" }, - { "X-Rate-Limit-Remaining", "100" }, - { "X-Rate-Limit-Reset", "1356998400" }, - { "X-Access-Level", "read-write-directmessages" }, + { "X-Rate-Limit-Limit", "150" }, + { "X-Rate-Limit-Remaining", "100" }, + { "X-Rate-Limit-Reset", "1356998400" }, + { "X-Access-Level", "read-write-directmessages" }, }, Content = new StringContent("\"hogehoge\""), }; @@ -156,9 +200,13 @@ public async Task GetAsync_UpdateRateLimitTest() var apiStatus = new TwitterApiStatus(); MyCommon.TwitterApiInfo = apiStatus; - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("hoge/tetete.json", UriKind.Relative), + EndpointName = "/hoge/tetete", + }; - await apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete"); + using var response = await apiConnection.SendAsync(request); Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel); Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]); @@ -167,7 +215,7 @@ public async Task GetAsync_UpdateRateLimitTest() } [Fact] - public async Task GetAsync_ErrorStatusTest() + public async Task SendAsync_ErrorStatusTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); @@ -182,10 +230,13 @@ public async Task GetAsync_ErrorStatusTest() }; }); - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("hoge/tetete.json", UriKind.Relative), + }; var exception = await Assert.ThrowsAsync( - () => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete") + () => apiConnection.SendAsync(request) ); // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する @@ -196,7 +247,7 @@ public async Task GetAsync_ErrorStatusTest() } [Fact] - public async Task GetAsync_ErrorJsonTest() + public async Task SendAsync_ErrorJsonTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); @@ -211,10 +262,13 @@ public async Task GetAsync_ErrorJsonTest() }; }); - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("hoge/tetete.json", UriKind.Relative), + }; var exception = await Assert.ThrowsAsync( - () => apiConnection.GetAsync(endpoint, null, endpointName: "/hoge/tetete") + () => apiConnection.SendAsync(request) ); // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する @@ -519,5 +573,76 @@ public async Task DeleteAsync_Test() Assert.Equal(0, mockHandler.QueueCount); } + + [Fact] + public async Task HandleTimeout_SuccessTest() + { + static async Task AsyncFunc(CancellationToken token) + { + await Task.Delay(10); + token.ThrowIfCancellationRequested(); + return 1; + } + + var timeout = TimeSpan.FromMilliseconds(200); + var ret = await TwitterApiConnection.HandleTimeout(AsyncFunc, timeout); + + Assert.Equal(1, ret); + } + + [Fact] + public async Task HandleTimeout_TimeoutTest() + { + var tcs = new TaskCompletionSource(); + + async Task AsyncFunc(CancellationToken token) + { + await Task.Delay(200); + tcs.SetResult(token.IsCancellationRequested); + return 1; + } + + var timeout = TimeSpan.FromMilliseconds(10); + await Assert.ThrowsAsync( + () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout) + ); + + var cancelRequested = await tcs.Task; + Assert.True(cancelRequested); + } + + [Fact] + public async Task HandleTimeout_ThrowExceptionAfterTimeoutTest() + { + var tcs = new TaskCompletionSource(); + + async Task AsyncFunc(CancellationToken token) + { + await Task.Delay(100); + tcs.SetResult(1); + throw new Exception(); + } + + var timeout = TimeSpan.FromMilliseconds(10); + await Assert.ThrowsAsync( + () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout) + ); + + // キャンセル後に AsyncFunc で発生した例外が無視される(UnobservedTaskException イベントを発生させない)ことをチェックする + var error = false; + void UnobservedExceptionHandler(object s, UnobservedTaskExceptionEventArgs e) + => error = true; + + TaskScheduler.UnobservedTaskException += UnobservedExceptionHandler; + + await tcs.Task; + await Task.Delay(10); + GC.Collect(); // UnobservedTaskException は Task のデストラクタで呼ばれるため強制的に GC を実行する + await Task.Delay(10); + + Assert.False(error); + + TaskScheduler.UnobservedTaskException -= UnobservedExceptionHandler; + } } } diff --git a/OpenTween.Tests/MyCommonTest.cs b/OpenTween.Tests/MyCommonTest.cs index 560c6a82b..1261d4730 100644 --- a/OpenTween.Tests/MyCommonTest.cs +++ b/OpenTween.Tests/MyCommonTest.cs @@ -135,9 +135,17 @@ public struct JsonData [Theory] [MemberData(nameof(CreateDataFromJsonTestCase))] - public void CreateDataFromJsonTest(string json, T expected) + public void CreateDataFromJson_StringTest(string json, T expected) => Assert.Equal(expected, MyCommon.CreateDataFromJson(json)); + [Theory] + [MemberData(nameof(CreateDataFromJsonTestCase))] + public void CreateDataFromJson_BytesTest(string json, T expected) + { + var jsonBytes = Encoding.UTF8.GetBytes(json); + Assert.Equal(expected, MyCommon.CreateDataFromJson(jsonBytes)); + } + [Theory] [InlineData("hoge123@example.com", true)] [InlineData("hogehoge", false)] diff --git a/OpenTween/Connection/ApiResponse.cs b/OpenTween/Connection/ApiResponse.cs new file mode 100644 index 000000000..3eabcb661 --- /dev/null +++ b/OpenTween/Connection/ApiResponse.cs @@ -0,0 +1,99 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using OpenTween.Api; + +namespace OpenTween.Connection +{ + public sealed class ApiResponse : IDisposable + { + public bool IsDisposed { get; private set; } + + private readonly HttpResponseMessage responseMessage; + + public ApiResponse(HttpResponseMessage responseMessage) + => this.responseMessage = responseMessage; + + public void Dispose() + { + if (this.IsDisposed) + return; + + this.responseMessage.Dispose(); + this.IsDisposed = true; + } + + public async Task ReadAsBytes() + { + using var content = this.responseMessage.Content; + + return await content.ReadAsByteArrayAsync() + .ConfigureAwait(false); + } + + public async Task ReadAsJson() + { + var responseBytes = await this.ReadAsBytes() + .ConfigureAwait(false); + + try + { + return MyCommon.CreateDataFromJson(responseBytes); + } + catch (SerializationException ex) + { + var responseText = Encoding.UTF8.GetString(responseBytes); + throw TwitterApiException.CreateFromException(ex, responseText); + } + } + + public async Task ReadAsJsonXml() + { + var responseBytes = await this.ReadAsBytes() + .ConfigureAwait(false); + + using var jsonReader = JsonReaderWriterFactory.CreateJsonReader( + responseBytes, + XmlDictionaryReaderQuotas.Max + ); + + try + { + return XElement.Load(jsonReader); + } + catch (XmlException ex) + { + var responseText = Encoding.UTF8.GetString(responseBytes); + throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText }; + } + } + } +} diff --git a/OpenTween/Connection/GetRequest.cs b/OpenTween/Connection/GetRequest.cs new file mode 100644 index 000000000..018354a13 --- /dev/null +++ b/OpenTween/Connection/GetRequest.cs @@ -0,0 +1,56 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class GetRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public IDictionary? Query { get; set; } + + public string? EndpointName { get; set; } + + public HttpRequestMessage CreateMessage(Uri baseUri) + => new() + { + Method = HttpMethod.Get, + RequestUri = BuildUriWithQuery(new(baseUri, this.RequestUri), this.Query), + }; + + public static Uri BuildUriWithQuery(Uri uri, IEnumerable>? query) + { + if (query == null) + return uri; + + if (!MyCommon.IsNullOrEmpty(uri.Query)) + throw new NotSupportedException("Merging uri query is not supported"); + + return new Uri(uri, "?" + MyCommon.BuildQueryString(query)); + } + } +} diff --git a/OpenTween/Connection/IApiConnection.cs b/OpenTween/Connection/IApiConnection.cs new file mode 100644 index 000000000..16e816668 --- /dev/null +++ b/OpenTween/Connection/IApiConnection.cs @@ -0,0 +1,33 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Threading.Tasks; + +namespace OpenTween.Connection +{ + public interface IApiConnection : IDisposable + { + Task SendAsync(IHttpRequest request); + } +} diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 9e2231840..4175e589d 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -30,7 +30,7 @@ namespace OpenTween.Connection { - public interface IApiConnectionLegacy : IDisposable + public interface IApiConnectionLegacy : IApiConnection, IDisposable { Task GetAsync(Uri uri, IDictionary? param, string? endpointName); diff --git a/OpenTween/Connection/IHttpRequest.cs b/OpenTween/Connection/IHttpRequest.cs new file mode 100644 index 000000000..55dd9e3b4 --- /dev/null +++ b/OpenTween/Connection/IHttpRequest.cs @@ -0,0 +1,35 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public interface IHttpRequest + { + string? EndpointName { get; } + + HttpRequestMessage CreateMessage(Uri baseUri); + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 338c8358a..091a32032 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -39,7 +39,7 @@ namespace OpenTween.Connection { - public class TwitterApiConnection : IApiConnectionLegacy, IDisposable + public class TwitterApiConnection : IApiConnection, IApiConnectionLegacy, IDisposable { public static Uri RestApiBase { get; set; } = new("https://api.twitter.com/1.1/"); @@ -98,42 +98,34 @@ private void InitializeHttpClients() this.HttpStreaming.Timeout = Timeout.InfiniteTimeSpan; } - public async Task GetAsync(Uri uri, IDictionary? param, string? endpointName) + public async Task SendAsync(IHttpRequest request) { + var endpointName = request.EndpointName; + // レートリミット規制中はAPIリクエストを送信せずに直ちにエラーを発生させる if (endpointName != null) this.ThrowIfRateLimitExceeded(endpointName); - var requestUri = new Uri(RestApiBase, uri); - - if (param != null) - requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param)); - - var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + using var requestMessage = request.CreateMessage(RestApiBase); + HttpResponseMessage? responseMessage = null; try { - using var response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); + responseMessage = await HandleTimeout( + (token) => this.Http.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, token), + Networking.DefaultTimeout + ); if (endpointName != null) - MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName); + MyCommon.TwitterApiInfo.UpdateFromHeader(responseMessage.Headers, endpointName); - await TwitterApiConnection.CheckStatusCode(response) + await TwitterApiConnection.CheckStatusCode(responseMessage) .ConfigureAwait(false); - using var content = response.Content; - var responseText = await content.ReadAsStringAsync() - .ConfigureAwait(false); + var response = new ApiResponse(responseMessage); + responseMessage = null; // responseMessage は ApiResponse で使用するため破棄されないようにする - try - { - return MyCommon.CreateDataFromJson(responseText); - } - catch (SerializationException ex) - { - throw TwitterApiException.CreateFromException(ex, responseText); - } + return response; } catch (HttpRequestException ex) { @@ -143,6 +135,26 @@ await TwitterApiConnection.CheckStatusCode(response) { throw TwitterApiException.CreateFromException(ex); } + finally + { + responseMessage?.Dispose(); + } + } + + public async Task GetAsync(Uri uri, IDictionary? param, string? endpointName) + { + var request = new GetRequest + { + RequestUri = uri, + Query = param, + EndpointName = endpointName, + }; + + using var response = await this.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } /// @@ -442,6 +454,38 @@ await TwitterApiConnection.CheckStatusCode(response) } } + public static async Task HandleTimeout(Func> func, TimeSpan timeout) + { + using var cts = new CancellationTokenSource(); + var cancellactionToken = cts.Token; + + var task = Task.Run(() => func(cancellactionToken), cancellactionToken); + var timeoutTask = Task.Delay(timeout); + + if (await Task.WhenAny(task, timeoutTask) == timeoutTask) + { + // タイムアウト + + // キャンセル後のタスクで発生した例外は無視する + static async Task IgnoreExceptions(Task task) + { + try + { + await task.ConfigureAwait(false); + } + catch + { + } + } + _ = IgnoreExceptions(task); + cts.Cancel(); + + throw new OperationCanceledException("Timeout", cancellactionToken); + } + + return await task; + } + protected static async Task CheckStatusCode(HttpResponseMessage response) { var statusCode = response.StatusCode; diff --git a/OpenTween/MyCommon.cs b/OpenTween/MyCommon.cs index 5e51960fe..e6b1f3d7c 100644 --- a/OpenTween/MyCommon.cs +++ b/OpenTween/MyCommon.cs @@ -719,9 +719,11 @@ public static DateTimeUtc DateTimeParse(string input) } public static T CreateDataFromJson(string content) + => MyCommon.CreateDataFromJson(Encoding.UTF8.GetBytes(content)); + + public static T CreateDataFromJson(byte[] bytes) { - var buf = Encoding.Unicode.GetBytes(content); - using var stream = new MemoryStream(buf); + using var stream = new MemoryStream(bytes); var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true, From d6556e13557fa5682ac4071ca662cca4fde42eb0 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 00:05:57 +0900 Subject: [PATCH 34/76] =?UTF-8?q?ListLatestTweetsTimeline=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConne?= =?UTF-8?q?ction=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ListLatestTweetsTimelineRequestTest.cs | 50 +++++++++---------- OpenTween.Tests/TestUtils.cs | 22 ++++++-- .../ListLatestTweetsTimelineRequest.cs | 36 +++++-------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs index 1a5965914..4cb9c952d 100644 --- a/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs @@ -19,14 +19,8 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; -using OpenTween.Api.TwitterV2; using OpenTween.Connection; using Xunit; @@ -37,21 +31,23 @@ public class ListLatestTweetsTimelineRequestTest [Fact] public async Task Send_Test() { - using var responseStream = File.OpenRead("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/6ClPnsuzQJ1p7-g32GQw9Q/ListLatestTweetsTimeline"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"listId":"1675863884757110790","count":20}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("ListLatestTweetsTimeline", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/6ClPnsuzQJ1p7-g32GQw9Q/ListLatestTweetsTimeline"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"listId":"1675863884757110790","count":20}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("ListLatestTweetsTimeline", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new ListLatestTweetsTimelineRequest(listId: "1675863884757110790") { @@ -69,21 +65,23 @@ public async Task Send_Test() [Fact] public async Task Send_RequestCursor_Test() { - using var responseStream = File.OpenRead("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/6ClPnsuzQJ1p7-g32GQw9Q/ListLatestTweetsTimeline"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"listId":"1675863884757110790","count":20,"cursor":"aaa"}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("ListLatestTweetsTimeline", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/6ClPnsuzQJ1p7-g32GQw9Q/ListLatestTweetsTimeline"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"listId":"1675863884757110790","count":20,"cursor":"aaa"}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("ListLatestTweetsTimeline", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new ListLatestTweetsTimelineRequest(listId: "1675863884757110790") { diff --git a/OpenTween.Tests/TestUtils.cs b/OpenTween.Tests/TestUtils.cs index c1c4e8869..9d5848a84 100644 --- a/OpenTween.Tests/TestUtils.cs +++ b/OpenTween.Tests/TestUtils.cs @@ -20,16 +20,16 @@ // Boston, MA 02110-1301, USA. using System; -using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Linq; +using System.Net; +using System.Net.Http; using System.Reflection; -using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using OpenTween.Connection; using Xunit; namespace OpenTween @@ -152,6 +152,22 @@ public void Dispose() public static DateTimeUtc LocalTime(int year, int month, int day, int hour, int minute, int second) => new(new DateTimeOffset(year, month, day, hour, minute, second, TimeZoneInfo.Local.BaseUtcOffset)); + + public static async Task CreateApiResponse(string path) + { + byte[] buffer; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length); + } + var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new ByteArrayContent(buffer), + }; + return new ApiResponse(responseMessage); + } } } diff --git a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs index fc1326be4..3aa306f6f 100644 --- a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs @@ -23,13 +23,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; @@ -84,26 +78,20 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var param = this.CreateParameters(); - - XElement rootElm; - try - { - using var stream = await apiConnection.GetStreamAsync(EndpointUri, param, EndpointName); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max); - rootElm = XElement.Load(jsonReader); - } - catch (IOException ex) - { - throw new WebApiException("IO Error", ex); - } - catch (NotSupportedException ex) + var request = new GetRequest { - // NotSupportedException: Stream does not support reading. のエラーが時々報告される - throw new WebApiException("Stream Error", ex); - } + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); ErrorResponse.ThrowIfError(rootElm); From d27426e4304b1d8ea7dc9c9c0cd8b6a7d22440cc Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 03:33:56 +0900 Subject: [PATCH 35/76] =?UTF-8?q?SearchTimeline=E3=81=AE=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConnection=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/SearchTimelineRequestTest.cs | 49 +++++++++---------- .../Api/GraphQL/SearchTimelineRequest.cs | 36 +++++--------- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs index 426df2741..7003b27c3 100644 --- a/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,21 +31,23 @@ public class SearchTimelineRequestTest [Fact] public async Task Send_Test() { - using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/SearchTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest"}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("SearchTimeline", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest"}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("SearchTimeline", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new SearchTimelineRequest(rawQuery: "#OpenTween") { @@ -68,21 +65,23 @@ public async Task Send_Test() [Fact] public async Task Send_RequestCursor_Test() { - using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/SearchTimeline_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest","cursor":"aaa"}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("SearchTimeline", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest","cursor":"aaa"}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("SearchTimeline", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new SearchTimelineRequest(rawQuery: "#OpenTween") { diff --git a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs index e157a2903..568371806 100644 --- a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs @@ -23,13 +23,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; @@ -86,26 +80,20 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var param = this.CreateParameters(); - - XElement rootElm; - try - { - using var stream = await apiConnection.GetStreamAsync(EndpointUri, param, EndpointName); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max); - rootElm = XElement.Load(jsonReader); - } - catch (IOException ex) - { - throw new WebApiException("IO Error", ex); - } - catch (NotSupportedException ex) + var request = new GetRequest { - // NotSupportedException: Stream does not support reading. のエラーが時々報告される - throw new WebApiException("Stream Error", ex); - } + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); ErrorResponse.ThrowIfError(rootElm); From 27d04a32c98c255ef436413acd3cf162d50ad3ce Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 03:38:26 +0900 Subject: [PATCH 36/76] =?UTF-8?q?TweetDetail=E3=81=AE=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConnection=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/TweetDetailRequestTest.cs | 23 +++++------- OpenTween/Api/GraphQL/TweetDetailRequest.cs | 37 ++++++------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs index 44aebf572..e88f82311 100644 --- a/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/TweetDetailRequestTest.cs @@ -19,14 +19,9 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; -using OpenTween.Api.TwitterV2; using OpenTween.Connection; using Xunit; @@ -37,19 +32,21 @@ public class TweetDetailRequestTest [Fact] public async Task Send_Test() { - using var responseStream = File.OpenRead("Resources/Responses/TweetDetail.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/TweetDetail.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/-Ls3CrSQNo2fRKH6i6Na1A/TweetDetail"), url); - Assert.Contains(@"""focalTweetId"":""1619433164757413894""", param["variables"]); - Assert.Equal("TweetDetail", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/-Ls3CrSQNo2fRKH6i6Na1A/TweetDetail"), request.RequestUri); + var query = request.Query!; + Assert.Contains(@"""focalTweetId"":""1619433164757413894""", query["variables"]); + Assert.Equal("TweetDetail", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new TweetDetailRequest { diff --git a/OpenTween/Api/GraphQL/TweetDetailRequest.cs b/OpenTween/Api/GraphQL/TweetDetailRequest.cs index 0bf750cce..b8c4151d3 100644 --- a/OpenTween/Api/GraphQL/TweetDetailRequest.cs +++ b/OpenTween/Api/GraphQL/TweetDetailRequest.cs @@ -23,14 +23,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; using OpenTween.Connection; using OpenTween.Models; @@ -60,26 +53,20 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var param = this.CreateParameters(); - - XElement rootElm; - try - { - using var stream = await apiConnection.GetStreamAsync(EndpointUri, param, EndpointName); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max); - rootElm = XElement.Load(jsonReader); - } - catch (IOException ex) - { - throw new WebApiException("IO Error", ex); - } - catch (NotSupportedException ex) + var request = new GetRequest { - // NotSupportedException: Stream does not support reading. のエラーが時々報告される - throw new WebApiException("Stream Error", ex); - } + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); ErrorResponse.ThrowIfError(rootElm); From 1f9813d0bc37ef18822c76dc503496d55adc98bf Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 03:43:37 +0900 Subject: [PATCH 37/76] =?UTF-8?q?UserByScreenName=E3=81=AE=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConnection?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GraphQL/UserByScreenNameRequestTest.cs | 31 ++++++++-------- .../Api/GraphQL/UserByScreenNameRequest.cs | 35 +++++++------------ 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs index 9f8ee7dff..9c4e287d9 100644 --- a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,19 +31,21 @@ public class UserByScreenNameRequestTest [Fact] public async Task Send_Test() { - using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UserByScreenName.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/xc8f1g7BYqr6VTzTbvNlGw/UserByScreenName"), url); - Assert.Contains(@"""screen_name"":""opentween""", param["variables"]); - Assert.Equal("UserByScreenName", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/xc8f1g7BYqr6VTzTbvNlGw/UserByScreenName"), request.RequestUri); + var query = request.Query!; + Assert.Contains(@"""screen_name"":""opentween""", query["variables"]); + Assert.Equal("UserByScreenName", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new UserByScreenNameRequest { @@ -64,13 +61,13 @@ public async Task Send_Test() [Fact] public async Task Send_UserUnavailableTest() { - using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName_Suspended.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UserByScreenName_Suspended.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new UserByScreenNameRequest { diff --git a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs index b4c04faf1..4b3c8ccad 100644 --- a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs +++ b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs @@ -23,12 +23,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; @@ -59,26 +54,20 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var param = this.CreateParameters(); - - XElement rootElm; - try - { - using var stream = await apiConnection.GetStreamAsync(EndpointUri, param, EndpointName); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max); - rootElm = XElement.Load(jsonReader); - } - catch (IOException ex) - { - throw new WebApiException("IO Error", ex); - } - catch (NotSupportedException ex) + var request = new GetRequest { - // NotSupportedException: Stream does not support reading. のエラーが時々報告される - throw new WebApiException("Stream Error", ex); - } + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); ErrorResponse.ThrowIfError(rootElm); From 0728818d688c7a575094f9e08ee916e2e401b839 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 03:48:02 +0900 Subject: [PATCH 38/76] =?UTF-8?q?UserTweetsAndReplies=E3=81=AE=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConnection?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserTweetsAndRepliesRequestTest.cs | 49 +++++++++---------- .../GraphQL/UserTweetsAndRepliesRequest.cs | 36 +++++--------- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs index 690cd4471..50264c609 100644 --- a/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,21 +31,23 @@ public class UserTweetsAndRepliesRequestTest [Fact] public async Task Send_Test() { - using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("UserTweetsAndReplies", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("UserTweetsAndReplies", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new UserTweetsAndRepliesRequest(userId: "40480664") { @@ -68,21 +65,23 @@ public async Task Send_Test() [Fact] public async Task Send_RequestCursor_Test() { - using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.GetStreamAsync(It.IsAny(), It.IsAny>(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback, string>((url, param, endpointName) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), url); - Assert.Equal(2, param.Count); - Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true,"cursor":"aaa"}""", param["variables"]); - Assert.True(param.ContainsKey("features")); - Assert.Equal("UserTweetsAndReplies", endpointName); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true,"cursor":"aaa"}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("UserTweetsAndReplies", request.EndpointName); }) - .ReturnsAsync(responseStream); + .ReturnsAsync(apiResponse); var request = new UserTweetsAndRepliesRequest(userId: "40480664") { diff --git a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs index 3fad57847..fff651342 100644 --- a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs +++ b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs @@ -23,13 +23,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; @@ -69,26 +63,20 @@ public Dictionary CreateParameters() }; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var param = this.CreateParameters(); - - XElement rootElm; - try - { - using var stream = await apiConnection.GetStreamAsync(EndpointUri, param, EndpointName); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max); - rootElm = XElement.Load(jsonReader); - } - catch (IOException ex) - { - throw new WebApiException("IO Error", ex); - } - catch (NotSupportedException ex) + var request = new GetRequest { - // NotSupportedException: Stream does not support reading. のエラーが時々報告される - throw new WebApiException("Stream Error", ex); - } + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); ErrorResponse.ThrowIfError(rootElm); From c3e2600c9804f0b24072341508a10aabc3cdbcd4 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 9 Dec 2023 04:43:44 +0900 Subject: [PATCH 39/76] =?UTF-8?q?ton.twitter.com=20=E3=81=AE=E3=82=B5?= =?UTF-8?q?=E3=83=A0=E3=83=8D=E3=82=A4=E3=83=AB=E7=94=BB=E5=83=8F=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E3=81=AB=E6=96=B0=E3=81=97=E3=81=84IApiConne?= =?UTF-8?q?ction=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thumbnail/Services/TonTwitterComTest.cs | 135 ++++++++++++++++++ OpenTween/Thumbnail/Services/TonTwitterCom.cs | 34 +++-- 2 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs diff --git a/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs b/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs new file mode 100644 index 000000000..235c9c823 --- /dev/null +++ b/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs @@ -0,0 +1,135 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using OpenTween.Connection; +using Xunit; + +namespace OpenTween.Thumbnail.Services +{ + public class TonTwitterComTest + { + [Fact] + public async Task GetThumbnailInfoAsync_Test() + { + var mock = new Mock(); + TonTwitterCom.GetApiConnection = () => mock.Object; + + var service = new TonTwitterCom(); + var thumb = await service.GetThumbnailInfoAsync( + "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg", + new(), + CancellationToken.None + ); + + Assert.NotNull(thumb!); + Assert.Equal( + "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", + thumb.MediaPageUrl + ); + Assert.Equal( + "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", + thumb.FullSizeImageUrl + ); + Assert.Equal( + "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg", + thumb.ThumbnailImageUrl + ); + + TonTwitterCom.GetApiConnection = null; + } + + [Fact] + public async Task GetThumbnailInfoAsync_ApiConnectionIsNotSetTest() + { + TonTwitterCom.GetApiConnection = null; + + var service = new TonTwitterCom(); + var thumb = await service.GetThumbnailInfoAsync( + "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg", + new(), + CancellationToken.None + ); + + Assert.Null(thumb); + } + + [Fact] + public async Task GetThumbnailInfoAsync_NotMatchedTest() + { + var mock = new Mock(); + TonTwitterCom.GetApiConnection = () => mock.Object; + + var service = new TonTwitterCom(); + var thumb = await service.GetThumbnailInfoAsync( + "https://example.com/abcdef.jpg", + new(), + CancellationToken.None + ); + + Assert.Null(thumb); + + TonTwitterCom.GetApiConnection = null; + } + + [Fact] + public async Task LoadThumbnailImageAsync_Test() + { + using var image = TestUtils.CreateDummyImage(); + using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(image.Stream.ToArray()), + }; + using var response = new ApiResponse(responseMessage); + + var mock = new Mock(); + mock.Setup( + x => x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal( + new("https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg"), + request.RequestUri + ); + }) + .ReturnsAsync(response); + + var apiConnection = mock.Object; + var thumb = new TonTwitterCom.Thumbnail(apiConnection) + { + MediaPageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", + FullSizeImageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", + ThumbnailImageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg", + }; + + var result = await thumb.LoadThumbnailImageAsync(CancellationToken.None); + Assert.Equal(image, result); + + mock.VerifyAll(); + } + } +} diff --git a/OpenTween/Thumbnail/Services/TonTwitterCom.cs b/OpenTween/Thumbnail/Services/TonTwitterCom.cs index b42844852..4e55b97f6 100644 --- a/OpenTween/Thumbnail/Services/TonTwitterCom.cs +++ b/OpenTween/Thumbnail/Services/TonTwitterCom.cs @@ -40,7 +40,7 @@ namespace OpenTween.Thumbnail.Services /// public class TonTwitterCom : IThumbnailService { - internal static Func? GetApiConnection; + internal static Func? GetApiConnection; public override Task GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token) { @@ -53,8 +53,9 @@ public class TonTwitterCom : IThumbnailService return null; var largeUrl = url + ":large"; + var apiConnection = GetApiConnection(); - return new TonTwitterCom.Thumbnail + return new TonTwitterCom.Thumbnail(apiConnection) { MediaPageUrl = largeUrl, ThumbnailImageUrl = url, @@ -67,22 +68,31 @@ public class TonTwitterCom : IThumbnailService public class Thumbnail : ThumbnailInfo { + private readonly IApiConnection apiConnection; + + public Thumbnail(IApiConnection apiConnection) + => this.apiConnection = apiConnection; + public override Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) { - return Task.Run( - async () => + return Task.Run(async () => + { + var request = new GetRequest { - var apiConnection = TonTwitterCom.GetApiConnection!(); + RequestUri = new(this.ThumbnailImageUrl), + }; + + using var response = await this.apiConnection.SendAsync(request) + .ConfigureAwait(false); - using var imageStream = await apiConnection.GetStreamAsync(new Uri(this.ThumbnailImageUrl), null) - .ConfigureAwait(false); + var imageBytes = await response.ReadAsBytes() + .ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - return await MemoryImage.CopyFromStreamAsync(imageStream) - .ConfigureAwait(false); - }, - cancellationToken); + return MemoryImage.CopyFromBytes(imageBytes); + }, + cancellationToken); } } } From 636921d32d240e5517fc4d19d90ec2f553640ba9 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 18:54:30 +0900 Subject: [PATCH 40/76] =?UTF-8?q?ApiResponse.ReadAsLazyJson=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Connection/ApiResponseTest.cs | 31 +++++++++++++++++++ OpenTween/Connection/ApiResponse.cs | 12 ++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/OpenTween.Tests/Connection/ApiResponseTest.cs b/OpenTween.Tests/Connection/ApiResponseTest.cs index a1ff5eee1..c281c593b 100644 --- a/OpenTween.Tests/Connection/ApiResponseTest.cs +++ b/OpenTween.Tests/Connection/ApiResponseTest.cs @@ -115,5 +115,36 @@ public async Task ReadAsJsonXml_InvalidJsonTest() ); Assert.Equal("### Invalid JSON Response ###", ex.ResponseText); } + + [Fact] + public async Task ReadAsLazyJson_Test() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + using var lazyJson = response.ReadAsLazyJson(); + Assert.Equal(new() { Foo = 123 }, await lazyJson.LoadJsonAsync()); + } + + [Fact] + public async Task ReadAsLazyJson_DisposeTest() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + using var lazyJson = response.ReadAsLazyJson(); + response.Dispose(); // ApiResponse を先に破棄しても LazyJson に影響しないことをテストする + + Assert.Equal(new() { Foo = 123 }, await lazyJson.LoadJsonAsync()); + } } } diff --git a/OpenTween/Connection/ApiResponse.cs b/OpenTween/Connection/ApiResponse.cs index 3eabcb661..7ba0ba27c 100644 --- a/OpenTween/Connection/ApiResponse.cs +++ b/OpenTween/Connection/ApiResponse.cs @@ -38,6 +38,7 @@ public sealed class ApiResponse : IDisposable public bool IsDisposed { get; private set; } private readonly HttpResponseMessage responseMessage; + private bool preventDisposeResponse; public ApiResponse(HttpResponseMessage responseMessage) => this.responseMessage = responseMessage; @@ -47,7 +48,9 @@ public void Dispose() if (this.IsDisposed) return; - this.responseMessage.Dispose(); + if (!this.preventDisposeResponse) + this.responseMessage.Dispose(); + this.IsDisposed = true; } @@ -95,5 +98,12 @@ public async Task ReadAsJsonXml() throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText }; } } + + public LazyJson ReadAsLazyJson() + { + this.preventDisposeResponse = true; + + return new(this.responseMessage); + } } } From 0f73783db8eb631177b7db475cfa0619bda75f6c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 19:16:02 +0900 Subject: [PATCH 41/76] =?UTF-8?q?ApiResponse.ReadAsString=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Connection/ApiResponseTest.cs | 14 ++++++++++++++ OpenTween/Connection/ApiResponse.cs | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/OpenTween.Tests/Connection/ApiResponseTest.cs b/OpenTween.Tests/Connection/ApiResponseTest.cs index c281c593b..ae3a8d8de 100644 --- a/OpenTween.Tests/Connection/ApiResponseTest.cs +++ b/OpenTween.Tests/Connection/ApiResponseTest.cs @@ -146,5 +146,19 @@ public async Task ReadAsLazyJson_DisposeTest() Assert.Equal(new() { Foo = 123 }, await lazyJson.LoadJsonAsync()); } + + [Fact] + public async Task ReadAsString_Test() + { + using var responseContent = new StringContent("foo"); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + Assert.Equal("foo", await response.ReadAsString()); + } } } diff --git a/OpenTween/Connection/ApiResponse.cs b/OpenTween/Connection/ApiResponse.cs index 7ba0ba27c..ee1560058 100644 --- a/OpenTween/Connection/ApiResponse.cs +++ b/OpenTween/Connection/ApiResponse.cs @@ -105,5 +105,13 @@ public LazyJson ReadAsLazyJson() return new(this.responseMessage); } + + public async Task ReadAsString() + { + using var content = this.responseMessage.Content; + + return await content.ReadAsStringAsync() + .ConfigureAwait(false); + } } } From c522ba5957c74f9c83f85e5a83b52123ce09c125 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 18:56:35 +0900 Subject: [PATCH 42/76] =?UTF-8?q?PostJsonRequest=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/PostJsonRequestTest.cs | 48 ++++++++++++++ OpenTween/Connection/PostJsonRequest.cs | 46 +++++++++++++ OpenTween/Connection/TwitterApiConnection.cs | 66 +++++-------------- 3 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 OpenTween.Tests/Connection/PostJsonRequestTest.cs create mode 100644 OpenTween/Connection/PostJsonRequest.cs diff --git a/OpenTween.Tests/Connection/PostJsonRequestTest.cs b/OpenTween.Tests/Connection/PostJsonRequestTest.cs new file mode 100644 index 000000000..d8b5f283e --- /dev/null +++ b/OpenTween.Tests/Connection/PostJsonRequestTest.cs @@ -0,0 +1,48 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Connection +{ + public class PostJsonRequestTest + { + [Fact] + public async Task CreateMessage_Test() + { + var request = new PostJsonRequest + { + RequestUri = new("aaa/bbb.json", UriKind.Relative), + JsonString = """{"foo":12345}""", + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Post, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/aaa/bbb.json"), requestMessage.RequestUri); + Assert.Equal("""{"foo":12345}""", await requestMessage.Content.ReadAsStringAsync()); + } + } +} diff --git a/OpenTween/Connection/PostJsonRequest.cs b/OpenTween/Connection/PostJsonRequest.cs new file mode 100644 index 000000000..715d54182 --- /dev/null +++ b/OpenTween/Connection/PostJsonRequest.cs @@ -0,0 +1,46 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Net.Http; +using System.Text; + +namespace OpenTween.Connection +{ + public class PostJsonRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public required string JsonString { get; set; } + + public string? EndpointName { get; set; } + + public HttpRequestMessage CreateMessage(Uri baseUri) + => new() + { + Method = HttpMethod.Post, + RequestUri = new(baseUri, this.RequestUri), + Content = new StringContent(this.JsonString, Encoding.UTF8, "application/json"), + }; + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 091a32032..35a8e0b94 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -368,67 +368,31 @@ await TwitterApiConnection.CheckStatusCode(response) public async Task PostJsonAsync(Uri uri, string json) { - var requestUri = new Uri(RestApiBase, uri); - using var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new StringContent(json, Encoding.UTF8, "application/json"); - request.Content = postContent; - - try + var request = new PostJsonRequest { - using var response = await this.Http.SendAsync(request) - .ConfigureAwait(false); + RequestUri = uri, + JsonString = json, + }; - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return await response.Content.ReadAsStringAsync() - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } + return await response.ReadAsString() + .ConfigureAwait(false); } public async Task> PostJsonAsync(Uri uri, string json) { - var requestUri = new Uri(RestApiBase, uri); - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new StringContent(json, Encoding.UTF8, "application/json"); - request.Content = postContent; - - HttpResponseMessage? response = null; - try + var request = new PostJsonRequest { - response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + RequestUri = uri, + JsonString = json, + }; - var result = new LazyJson(response); - response = null; + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return result; - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - finally - { - response?.Dispose(); - } + return response.ReadAsLazyJson(); } public async Task DeleteAsync(Uri uri) From b7475a783892406176047e2aca7da6b9e9434105 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 18:28:28 +0900 Subject: [PATCH 43/76] =?UTF-8?q?GraphQL=E9=96=A2=E9=80=A3=E3=81=AE?= =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88=E3=81=ABPostJsonR?= =?UTF-8?q?equest=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/CreateRetweetRequestTest.cs | 20 +++----- .../Api/GraphQL/CreateTweetRequestTest.cs | 50 +++++++++---------- .../Api/GraphQL/DeleteRetweetRequestTest.cs | 21 ++++---- .../Api/GraphQL/DeleteTweetRequestTest.cs | 21 ++++---- OpenTween.Tests/OpenTween.Tests.csproj | 6 +++ .../Resources/Responses/DeleteRetweet.json | 14 ++++++ .../Resources/Responses/DeleteTweet.json | 7 +++ OpenTween/Api/GraphQL/CreateRetweetRequest.cs | 25 +++++----- OpenTween/Api/GraphQL/CreateTweetRequest.cs | 23 +++++---- OpenTween/Api/GraphQL/DeleteRetweetRequest.cs | 22 +++++--- OpenTween/Api/GraphQL/DeleteTweetRequest.cs | 22 +++++--- 11 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 OpenTween.Tests/Resources/Responses/DeleteRetweet.json create mode 100644 OpenTween.Tests/Resources/Responses/DeleteTweet.json diff --git a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs index 5fee38558..233fd0143 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,18 +31,19 @@ public class CreateRetweetRequestTest [Fact] public async Task Send_Test() { - var responseText = File.ReadAllText("Resources/Responses/CreateRetweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateRetweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet"), url); - Assert.Contains(@"""tweet_id"":""12345""", json); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet"), request.RequestUri); + Assert.Contains(@"""tweet_id"":""12345""", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateRetweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs index ad08aec06..b01e95a7f 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,20 +31,21 @@ public class CreateTweetRequestTest [Fact] public async Task Send_Test() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/tTsjMKyhajZvK4q76mpIBg/CreateTweet"), url); - Assert.Contains(@"""tweet_text"":""tetete""", json); - Assert.DoesNotContain(@"""reply"":", json); - Assert.DoesNotContain(@"""media"":", json); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/tTsjMKyhajZvK4q76mpIBg/CreateTweet"), request.RequestUri); + Assert.Contains(@"""tweet_text"":""tetete""", request.JsonString); + Assert.DoesNotContain(@"""reply"":", request.JsonString); + Assert.DoesNotContain(@"""media"":", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { @@ -65,17 +61,18 @@ public async Task Send_Test() [Fact] public async Task Send_ReplyTest() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Contains(@"""reply"":{""exclude_reply_user_ids"":[""11111"",""22222""],""in_reply_to_tweet_id"":""12345""}", json); + var request = Assert.IsType(x); + Assert.Contains(@"""reply"":{""exclude_reply_user_ids"":[""11111"",""22222""],""in_reply_to_tweet_id"":""12345""}", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { @@ -90,17 +87,18 @@ public async Task Send_ReplyTest() [Fact] public async Task Send_MediaTest() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Contains(@"""media"":{""media_entities"":[{""media_id"":""11111"",""tagged_users"":[]},{""media_id"":""22222"",""tagged_users"":[]}],""possibly_sensitive"":false}", json); + var request = Assert.IsType(x); + Assert.Contains(@"""media"":{""media_entities"":[{""media_id"":""11111"",""tagged_users"":[]},{""media_id"":""22222"",""tagged_users"":[]}],""possibly_sensitive"":false}", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs index 9a52489d3..eceafb83a 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,15 +31,19 @@ public class DeleteRetweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/DeleteRetweet.json"); + + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet"), url); - Assert.Contains(@"""source_tweet_id"":""12345""", json); - }); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet"), request.RequestUri); + Assert.Contains(@"""source_tweet_id"":""12345""", request.JsonString); + }) + .ReturnsAsync(apiResponse); var request = new DeleteRetweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs index 3db62ccec..6cd25806b 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,15 +31,19 @@ public class DeleteTweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/DeleteTweet.json"); + + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet"), url); - Assert.Contains(@"""tweet_id"":""12345""", json); - }); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet"), request.RequestUri); + Assert.Contains(@"""tweet_id"":""12345""", request.JsonString); + }) + .ReturnsAsync(apiResponse); var request = new DeleteTweetRequest { diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index 70754bdeb..dbaad23f1 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -59,6 +59,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/OpenTween.Tests/Resources/Responses/DeleteRetweet.json b/OpenTween.Tests/Resources/Responses/DeleteRetweet.json new file mode 100644 index 000000000..f125b0470 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/DeleteRetweet.json @@ -0,0 +1,14 @@ +{ + "data": { + "unretweet": { + "source_tweet_results": { + "result": { + "rest_id": "1234567890123456789", + "legacy": { + "full_text": "foo" + } + } + } + } + } +} diff --git a/OpenTween.Tests/Resources/Responses/DeleteTweet.json b/OpenTween.Tests/Resources/Responses/DeleteTweet.json new file mode 100644 index 000000000..919d638ea --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/DeleteTweet.json @@ -0,0 +1,7 @@ +{ + "data": { + "delete_tweet": { + "tweet_results": {} + } + } +} diff --git a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs index 35b5060e2..16df55b24 100644 --- a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs @@ -22,14 +22,7 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; using OpenTween.Models; @@ -49,14 +42,20 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var response = await apiConnection.PostJsonAsync(EndpointUri, json); - var responseBytes = Encoding.UTF8.GetBytes(response); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); - var rootElm = XElement.Load(jsonReader); ErrorResponse.ThrowIfError(rootElm); var tweetIdStr = rootElm.XPathSelectElement("/data/create_retweet/retweet_results/result/rest_id")?.Value ?? throw CreateParseError(); diff --git a/OpenTween/Api/GraphQL/CreateTweetRequest.cs b/OpenTween/Api/GraphQL/CreateTweetRequest.cs index 46e1075ac..5caf3ac22 100644 --- a/OpenTween/Api/GraphQL/CreateTweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateTweetRequest.cs @@ -23,14 +23,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Api.DataModel; using OpenTween.Connection; @@ -153,14 +148,20 @@ public string CreateRequestBody() return JsonUtils.SerializeJsonByDataContract(body); } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var response = await apiConnection.PostJsonAsync(EndpointUri, json); - var responseBytes = Encoding.UTF8.GetBytes(response); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); - var rootElm = XElement.Load(jsonReader); ErrorResponse.ThrowIfError(rootElm); var tweetElm = rootElm.XPathSelectElement("/data/create_tweet/tweet_results/result") ?? throw CreateParseError(); diff --git a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs index 00a0b8368..500ed372e 100644 --- a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs @@ -22,10 +22,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using OpenTween.Connection; using OpenTween.Models; @@ -45,11 +41,21 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); - ErrorResponse.ThrowIfError(responseText); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + ErrorResponse.ThrowIfError(rootElm); } } } diff --git a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs index adccacec0..db3b28c63 100644 --- a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs @@ -22,10 +22,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using OpenTween.Connection; using OpenTween.Models; @@ -45,11 +41,21 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); - ErrorResponse.ThrowIfError(responseText); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + ErrorResponse.ThrowIfError(rootElm); } } } From 3a9a903888fab7f4f0bd9e120280c5509ddd56bd Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 20:53:35 +0900 Subject: [PATCH 44/76] =?UTF-8?q?SettingCommon=E3=81=AEToken,=20TokenSecre?= =?UTF-8?q?t=E3=81=AE=E6=9B=B4=E6=96=B0=E3=82=92TweenMain=E3=81=8B?= =?UTF-8?q?=E3=82=89=E8=A1=8C=E3=82=8F=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BasedPanel.SaveConfig メソッド内でこれらのプロパティが更新されるため不要な処理になっている --- OpenTween/Tween.cs | 4 ---- OpenTween/Twitter.cs | 6 ------ 2 files changed, 10 deletions(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index efadf0c97..63e298830 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -5695,10 +5695,6 @@ private void SaveConfigsCommon() this.ModifySettingCommon = false; lock (this.syncObject) { - this.settings.Common.UserName = this.tw.Username; - this.settings.Common.UserId = this.tw.UserId; - this.settings.Common.Token = this.tw.AccessToken; - this.settings.Common.TokenSecret = this.tw.AccessTokenSecret; this.settings.Common.SortOrder = (int)this.statuses.SortOrder; this.settings.Common.SortColumn = this.statuses.SortMode switch { diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 4e34545ba..f100a8877 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -1437,12 +1437,6 @@ public async Task RefreshMuteUserIdsAsync() public string[] GetHashList() => this.postFactory.GetReceivedHashtags(); - public string AccessToken - => ((TwitterApiConnection)this.Api.Connection).AccessToken; - - public string AccessTokenSecret - => ((TwitterApiConnection)this.Api.Connection).AccessSecret; - private void CheckAccountState() { if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) From b43c97bf6ed9af79b47fdbfe6ea23fdb3ca3b160 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 22:03:16 +0900 Subject: [PATCH 45/76] =?UTF-8?q?ITwitterCredential=E3=81=A8=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=BB=E3=82=B9=E6=89=8B=E6=AE=B5=E3=81=94=E3=81=A8?= =?UTF-8?q?=E3=81=AE=E5=85=B7=E8=B1=A1=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 10 +- .../Connection/TwitterApiConnectionTest.cs | 26 ++-- .../Connection/TwitterCredentialCookieTest.cs | 47 ++++++++ .../Connection/TwitterCredentialOAuth1Test.cs | 51 ++++++++ OpenTween.Tests/TweenMainTest.cs | 8 +- OpenTween/Api/TwitterApi.cs | 8 +- OpenTween/AppendSettingDialog.cs | 4 +- OpenTween/ApplicationEvents.cs | 4 +- OpenTween/Connection/ITwitterCredential.cs | 34 ++++++ OpenTween/Connection/TwitterApiConnection.cs | 113 +++++++----------- .../Connection/TwitterCredentialCookie.cs | 38 ++++++ OpenTween/Connection/TwitterCredentialNone.cs | 36 ++++++ .../Connection/TwitterCredentialOAuth1.cs | 52 ++++++++ OpenTween/Setting/SettingCommon.cs | 14 +++ OpenTween/Tween.cs | 4 +- OpenTween/Twitter.cs | 9 +- 16 files changed, 349 insertions(+), 109 deletions(-) create mode 100644 OpenTween.Tests/Connection/TwitterCredentialCookieTest.cs create mode 100644 OpenTween.Tests/Connection/TwitterCredentialOAuth1Test.cs create mode 100644 OpenTween/Connection/ITwitterCredential.cs create mode 100644 OpenTween/Connection/TwitterCredentialCookie.cs create mode 100644 OpenTween/Connection/TwitterCredentialNone.cs create mode 100644 OpenTween/Connection/TwitterCredentialOAuth1.cs diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index d74088b2f..bac959bb4 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -59,8 +59,9 @@ public void Initialize_Test() Assert.IsType(twitterApi.ApiConnection); var apiConnection = (TwitterApiConnection)twitterApi.ApiConnection!; - Assert.Equal("*** AccessToken ***", apiConnection.AccessToken); - Assert.Equal("*** AccessSecret ***", apiConnection.AccessSecret); + var credential = Assert.IsType(apiConnection.Credential); + Assert.Equal("*** AccessToken ***", credential.Token); + Assert.Equal("*** AccessSecret ***", credential.TokenSecret); Assert.Equal(100L, twitterApi.CurrentUserId); Assert.Equal("hogehoge", twitterApi.CurrentScreenName); @@ -74,8 +75,9 @@ public void Initialize_Test() Assert.IsType(twitterApi.ApiConnection); apiConnection = (TwitterApiConnection)twitterApi.ApiConnection!; - Assert.Equal("*** AccessToken2 ***", apiConnection.AccessToken); - Assert.Equal("*** AccessSecret2 ***", apiConnection.AccessSecret); + credential = Assert.IsType(apiConnection.Credential); + Assert.Equal("*** AccessToken2 ***", credential.Token); + Assert.Equal("*** AccessSecret2 ***", credential.TokenSecret); Assert.Equal(200L, twitterApi.CurrentUserId); Assert.Equal("foobar", twitterApi.CurrentScreenName); diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index 1c7b53cdf..c646f07bb 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -57,7 +57,7 @@ public async Task GetAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -95,7 +95,7 @@ public async Task GetAsync_AbsoluteUriTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -132,7 +132,7 @@ public async Task SendAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -175,7 +175,7 @@ public async Task SendAsync_UpdateRateLimitTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -219,7 +219,7 @@ public async Task SendAsync_ErrorStatusTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -251,7 +251,7 @@ public async Task SendAsync_ErrorJsonTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => @@ -285,7 +285,7 @@ public async Task GetStreamAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); using var image = TestUtils.CreateDummyImage(); apiConnection.Http = http; @@ -331,7 +331,7 @@ public async Task PostLazyAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(async x => @@ -371,7 +371,7 @@ public async Task PostLazyAsync_MultipartTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.HttpUpload = http; using var image = TestUtils.CreateDummyImage(); @@ -441,7 +441,7 @@ public async Task PostLazyAsync_Multipart_NullTest() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.HttpUpload = http; mockHandler.Enqueue(async x => @@ -486,7 +486,7 @@ public async Task PostJsonAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(async x => @@ -520,7 +520,7 @@ public async Task PostJsonAsync_T_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(async x => @@ -555,7 +555,7 @@ public async Task DeleteAsync_Test() { using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", ""); + using var apiConnection = new TwitterApiConnection(); apiConnection.Http = http; mockHandler.Enqueue(x => diff --git a/OpenTween.Tests/Connection/TwitterCredentialCookieTest.cs b/OpenTween.Tests/Connection/TwitterCredentialCookieTest.cs new file mode 100644 index 000000000..938b8f553 --- /dev/null +++ b/OpenTween.Tests/Connection/TwitterCredentialCookieTest.cs @@ -0,0 +1,47 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System.Net.Http; +using Xunit; + +namespace OpenTween.Connection +{ + public class TwitterCredentialCookieTest + { + [Fact] + public void CreateHttpHandler_Test() + { + var appToken = new TwitterAppToken + { + AuthType = APIAuthType.TwitterComCookie, + TwitterComCookie = "aaa=bbb", + }; + var credential = new TwitterCredentialCookie(appToken); + + using var innerHandler = new HttpClientHandler(); + using var handler = credential.CreateHttpHandler(innerHandler); + + var cookieHandler = Assert.IsType(handler); + Assert.Equal("aaa=bbb", cookieHandler.RawCookie); + Assert.Same(innerHandler, cookieHandler.InnerHandler); + } + } +} diff --git a/OpenTween.Tests/Connection/TwitterCredentialOAuth1Test.cs b/OpenTween.Tests/Connection/TwitterCredentialOAuth1Test.cs new file mode 100644 index 000000000..5ede503b5 --- /dev/null +++ b/OpenTween.Tests/Connection/TwitterCredentialOAuth1Test.cs @@ -0,0 +1,51 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System.Net.Http; +using Xunit; + +namespace OpenTween.Connection +{ + public class TwitterCredentialOAuth1Test + { + [Fact] + public void CreateHttpHandler_Test() + { + var appToken = new TwitterAppToken + { + AuthType = APIAuthType.OAuth1, + OAuth1CustomConsumerKey = ApiKey.Create("consumer_key"), + OAuth1CustomConsumerSecret = ApiKey.Create("consumer_secret"), + }; + var credential = new TwitterCredentialOAuth1(appToken, "access_token", "access_secret"); + + using var innerHandler = new HttpClientHandler(); + using var handler = credential.CreateHttpHandler(innerHandler); + + var oauthHandler = Assert.IsType(handler); + Assert.Equal("consumer_key", oauthHandler.ConsumerKey.Value); + Assert.Equal("consumer_secret", oauthHandler.ConsumerSecret.Value); + Assert.Equal("access_token", oauthHandler.AccessToken); + Assert.Equal("access_secret", oauthHandler.AccessSecret); + Assert.Same(innerHandler, oauthHandler.InnerHandler); + } + } +} diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index 37a1ac2f3..87dfbec72 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -48,13 +48,7 @@ public void Initialize_Test() using var imageCache = new ImageCache(); using var iconAssets = new IconAssetsManager(); var thumbnailGenerator = new ThumbnailGenerator(new(autoupdate: false)); - var twitterAppToken = new TwitterAppToken - { - AuthType = APIAuthType.OAuth1, - OAuth1CustomConsumerKey = ApiKey.Create("aaa"), - OAuth1CustomConsumerSecret = ApiKey.Create("bbb"), - }; - twitter.Initialize(twitterAppToken, "", "", "", 0L); + twitter.Initialize(new TwitterCredentialNone(), "", 0L); using var tweenMain = new TweenMain(settings, tabinfo, twitter, imageCache, iconAssets, thumbnailGenerator); } diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index abce76331..bc97384de 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -62,13 +62,13 @@ public TwitterApi(ApiKey consumerKey, ApiKey consumerSecret) } public void Initialize(string accessToken, string accessSecret, long userId, string screenName) - => this.Initialize(this.AppToken, accessToken, accessSecret, userId, screenName); + => this.Initialize(new TwitterCredentialOAuth1(this.AppToken, accessToken, accessSecret), userId, screenName); - public void Initialize(TwitterAppToken appToken, string accessToken, string accessSecret, long userId, string screenName) + public void Initialize(ITwitterCredential credential, long userId, string screenName) { - this.AppToken = appToken; + this.AppToken = credential.AppToken; - var newInstance = new TwitterApiConnection(this.AppToken, accessToken, accessSecret); + var newInstance = new TwitterApiConnection(credential); var oldInstance = Interlocked.Exchange(ref this.ApiConnection, newInstance); oldInstance?.Dispose(); diff --git a/OpenTween/AppendSettingDialog.cs b/OpenTween/AppendSettingDialog.cs index 0803eb487..703cd51c0 100644 --- a/OpenTween/AppendSettingDialog.cs +++ b/OpenTween/AppendSettingDialog.cs @@ -195,7 +195,7 @@ private async void StartAuthButton_Click(object sender, EventArgs e) }; using var twitterApi = new TwitterApi(); - twitterApi.Initialize(appToken, "", "", 0L, ""); + twitterApi.Initialize(new TwitterCredentialCookie(appToken), 0L, ""); var twitterUser = await twitterApi.AccountVerifyCredentials(); newAccount.UserId = twitterUser.Id; newAccount.Username = twitterUser.ScreenName; @@ -294,7 +294,7 @@ public void ApplyNetworkSettings() if (MyCommon.IsNullOrEmpty(pin)) return null; // キャンセルされた場合 - var accessTokenResponse = await TwitterApiConnection.GetAccessTokenAsync(appToken, requestToken, pin); + var accessTokenResponse = await TwitterApiConnection.GetAccessTokenAsync(requestToken, pin); return new UserAccount { diff --git a/OpenTween/ApplicationEvents.cs b/OpenTween/ApplicationEvents.cs index 539639ccc..04d5a6946 100644 --- a/OpenTween/ApplicationEvents.cs +++ b/OpenTween/ApplicationEvents.cs @@ -143,9 +143,9 @@ private static void SetupTwitter(Twitter tw, SettingManager settings) { var account = settings.Common.SelectedAccount; if (account != null) - tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId); + tw.Initialize(account.GetTwitterCredential(), account.Username, account.UserId); else - tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L); + tw.Initialize(new TwitterCredentialNone(), "", 0L); tw.RestrictFavCheck = settings.Common.RestrictFavCheck; tw.ReadOwnPost = settings.Common.ReadOwnPost; diff --git a/OpenTween/Connection/ITwitterCredential.cs b/OpenTween/Connection/ITwitterCredential.cs new file mode 100644 index 000000000..87f5a8dc8 --- /dev/null +++ b/OpenTween/Connection/ITwitterCredential.cs @@ -0,0 +1,34 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System.Net.Http; + +namespace OpenTween.Connection +{ + public interface ITwitterCredential + { + TwitterAppToken AppToken { get; } + + HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler); + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 35a8e0b94..bfcc99b0d 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -52,35 +52,20 @@ public static string RestApiHost public bool IsDisposed { get; private set; } = false; - public string AccessToken { get; } - - public string AccessSecret { get; } - internal HttpClient Http; internal HttpClient HttpUpload; internal HttpClient HttpStreaming; - private readonly TwitterAppToken appToken; + internal ITwitterCredential Credential { get; } - public TwitterApiConnection(ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret) - : this( - new() - { - AuthType = APIAuthType.OAuth1, - OAuth1CustomConsumerKey = consumerKey, - OAuth1CustomConsumerSecret = consumerSecret, - }, - accessToken, - accessSecret - ) + public TwitterApiConnection() + : this(new TwitterCredentialNone()) { } - public TwitterApiConnection(TwitterAppToken appToken, string accessToken, string accessSecret) + public TwitterApiConnection(ITwitterCredential credential) { - this.appToken = appToken; - this.AccessToken = accessToken; - this.AccessSecret = accessSecret; + this.Credential = credential; this.InitializeHttpClients(); Networking.WebProxyChanged += this.Networking_WebProxyChanged; @@ -89,12 +74,12 @@ public TwitterApiConnection(TwitterAppToken appToken, string accessToken, string [MemberNotNull(nameof(Http), nameof(HttpUpload), nameof(HttpStreaming))] private void InitializeHttpClients() { - this.Http = InitializeHttpClient(this.appToken, this.AccessToken, this.AccessSecret); + this.Http = InitializeHttpClient(this.Credential); - this.HttpUpload = InitializeHttpClient(this.appToken, this.AccessToken, this.AccessSecret); + this.HttpUpload = InitializeHttpClient(this.Credential); this.HttpUpload.Timeout = Networking.UploadImageTimeout; - this.HttpStreaming = InitializeHttpClient(this.appToken, this.AccessToken, this.AccessSecret, disableGzip: true); + this.HttpStreaming = InitializeHttpClient(this.Credential, disableGzip: true); this.HttpStreaming.Timeout = Timeout.InfiniteTimeSpan; } @@ -500,14 +485,29 @@ public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, { var uri = new Uri(RestApiBase, authServiceProvider); - return OAuthEchoHandler.CreateHandler( - innerHandler, - uri, - this.appToken.OAuth1ConsumerKey, - this.appToken.OAuth1ConsumerSecret, - this.AccessToken, - this.AccessSecret, - realm); + if (this.Credential is TwitterCredentialOAuth1 oauthCredential) + { + return OAuthEchoHandler.CreateHandler( + innerHandler, + uri, + oauthCredential.AppToken.OAuth1ConsumerKey, + oauthCredential.AppToken.OAuth1ConsumerSecret, + oauthCredential.Token, + oauthCredential.TokenSecret, + realm); + } + else + { + // MobipictureApi クラス向けの暫定対応 + return OAuthEchoHandler.CreateHandler( + innerHandler, + uri, + ApiKey.Create(""), + ApiKey.Create(""), + "", + "", + realm); + } } public void Dispose() @@ -538,19 +538,20 @@ protected virtual void Dispose(bool disposing) private void Networking_WebProxyChanged(object sender, EventArgs e) => this.InitializeHttpClients(); - public static async Task<(string Token, string TokenSecret)> GetRequestTokenAsync(TwitterAppToken appToken) + public static async Task GetRequestTokenAsync(TwitterAppToken appToken) { + var emptyCredential = new TwitterCredentialOAuth1(appToken, "", ""); var param = new Dictionary { ["oauth_callback"] = "oob", }; - var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, appToken, oauthToken: null) + var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, emptyCredential) .ConfigureAwait(false); - return (response["oauth_token"], response["oauth_token_secret"]); + return new(appToken, response["oauth_token"], response["oauth_token_secret"]); } - public static Uri GetAuthorizeUri((string Token, string TokenSecret) requestToken, string? screenName = null) + public static Uri GetAuthorizeUri(TwitterCredentialOAuth1 requestToken, string? screenName = null) { var param = new Dictionary { @@ -563,13 +564,13 @@ public static Uri GetAuthorizeUri((string Token, string TokenSecret) requestToke return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param)); } - public static async Task> GetAccessTokenAsync(TwitterAppToken appToken, (string Token, string TokenSecret) requestToken, string verifier) + public static async Task> GetAccessTokenAsync(TwitterCredentialOAuth1 credential, string verifier) { var param = new Dictionary { ["oauth_verifier"] = verifier, }; - var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, appToken, requestToken) + var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, credential) .ConfigureAwait(false); return response; @@ -578,14 +579,10 @@ public static async Task> GetAccessTokenAsync(Twitte private static async Task> GetOAuthTokenAsync( Uri uri, IDictionary param, - TwitterAppToken appToken, - (string Token, string TokenSecret)? oauthToken) + TwitterCredentialOAuth1 credential + ) { - HttpClient authorizeClient; - if (oauthToken != null) - authorizeClient = InitializeHttpClient(appToken.OAuth1ConsumerKey, appToken.OAuth1ConsumerSecret, oauthToken.Value.Token, oauthToken.Value.TokenSecret); - else - authorizeClient = InitializeHttpClient(appToken.OAuth1ConsumerKey, appToken.OAuth1ConsumerSecret, "", ""); + using var authorizeClient = InitializeHttpClient(credential); var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param)); @@ -617,24 +614,7 @@ await TwitterApiConnection.CheckStatusCode(response) } } - private static HttpClient InitializeHttpClient(ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret, bool disableGzip = false) - { - var builder = Networking.CreateHttpClientBuilder(); - - builder.SetupHttpClientHandler(x => - { - x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); - - if (disableGzip) - x.AutomaticDecompression = DecompressionMethods.None; - }); - - builder.AddHandler(x => new OAuthHandler(x, consumerKey, consumerSecret, accessToken, accessSecret)); - - return builder.Build(); - } - - private static HttpClient InitializeHttpClient(TwitterAppToken appToken, string accessToken, string accessSecret, bool disableGzip = false) + private static HttpClient InitializeHttpClient(ITwitterCredential credential, bool disableGzip = false) { var builder = Networking.CreateHttpClientBuilder(); @@ -646,14 +626,7 @@ private static HttpClient InitializeHttpClient(TwitterAppToken appToken, string x.AutomaticDecompression = DecompressionMethods.None; }); - builder.AddHandler(x => appToken.AuthType switch - { - APIAuthType.OAuth1 - => new OAuthHandler(x, appToken.OAuth1ConsumerKey, appToken.OAuth1ConsumerSecret, accessToken, accessSecret), - APIAuthType.TwitterComCookie - => new TwitterComCookieHandler(x, appToken.TwitterComCookie), - _ => throw new NotImplementedException(), - }); + builder.AddHandler(x => credential.CreateHttpHandler(x)); return builder.Build(); } diff --git a/OpenTween/Connection/TwitterCredentialCookie.cs b/OpenTween/Connection/TwitterCredentialCookie.cs new file mode 100644 index 000000000..14452e096 --- /dev/null +++ b/OpenTween/Connection/TwitterCredentialCookie.cs @@ -0,0 +1,38 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class TwitterCredentialCookie : ITwitterCredential + { + public TwitterAppToken AppToken { get; } + + public TwitterCredentialCookie(TwitterAppToken appToken) + => this.AppToken = appToken; + + public HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler) + => new TwitterComCookieHandler(innerHandler, this.AppToken.TwitterComCookie); + } +} diff --git a/OpenTween/Connection/TwitterCredentialNone.cs b/OpenTween/Connection/TwitterCredentialNone.cs new file mode 100644 index 000000000..67d1c86be --- /dev/null +++ b/OpenTween/Connection/TwitterCredentialNone.cs @@ -0,0 +1,36 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class TwitterCredentialNone : ITwitterCredential + { + public TwitterAppToken AppToken + => TwitterAppToken.GetDefault(); + + public HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler) + => innerHandler; + } +} diff --git a/OpenTween/Connection/TwitterCredentialOAuth1.cs b/OpenTween/Connection/TwitterCredentialOAuth1.cs new file mode 100644 index 000000000..298ace003 --- /dev/null +++ b/OpenTween/Connection/TwitterCredentialOAuth1.cs @@ -0,0 +1,52 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class TwitterCredentialOAuth1 : ITwitterCredential + { + public TwitterAppToken AppToken { get; } + + public string Token { get; } + + public string TokenSecret { get; } + + public TwitterCredentialOAuth1(TwitterAppToken appToken, string accessToken, string accessSecret) + { + this.AppToken = appToken; + this.Token = accessToken; + this.TokenSecret = accessSecret; + } + + public HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler) + => new OAuthHandler( + innerHandler, + this.AppToken.OAuth1ConsumerKey, + this.AppToken.OAuth1ConsumerSecret, + this.Token, + this.TokenSecret + ); + } +} diff --git a/OpenTween/Setting/SettingCommon.cs b/OpenTween/Setting/SettingCommon.cs index 3e9f65684..5d9f3ad7b 100644 --- a/OpenTween/Setting/SettingCommon.cs +++ b/OpenTween/Setting/SettingCommon.cs @@ -398,6 +398,20 @@ public TwitterAppToken GetTwitterAppToken() }; } + public ITwitterCredential GetTwitterCredential() + { + var appToken = this.GetTwitterAppToken(); + + return appToken.AuthType switch + { + APIAuthType.OAuth1 + => new TwitterCredentialOAuth1(appToken, this.TwitterOAuth1ConsumerKey, this.TwitterOAuth1ConsumerSecret), + APIAuthType.TwitterComCookie + => new TwitterCredentialCookie(appToken), + _ => new TwitterCredentialNone(), + }; + } + private string Encrypt(string password) { if (MyCommon.IsNullOrEmpty(password)) password = ""; diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 63e298830..0dc356877 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -2555,9 +2555,9 @@ private async void SettingStripMenuItem_Click(object sender, EventArgs e) var account = this.settings.Common.SelectedAccount; if (account != null) - this.tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId); + this.tw.Initialize(account.GetTwitterCredential(), account.Username, account.UserId); else - this.tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L); + this.tw.Initialize(new TwitterCredentialNone(), "", 0L); this.tw.RestrictFavCheck = this.settings.Common.RestrictFavCheck; this.tw.ReadOwnPost = this.settings.Common.ReadOwnPost; diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index f100a8877..48be5ba98 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -228,15 +228,14 @@ public void Initialize(string token, string tokenSecret, string username, long u this.Api.Initialize(token, tokenSecret, userId, username); } - public void Initialize(TwitterAppToken appToken, string token, string tokenSecret, string username, long userId) + public void Initialize(ITwitterCredential credential, string username, long userId) { // OAuth認証 - if (MyCommon.IsNullOrEmpty(token) || MyCommon.IsNullOrEmpty(tokenSecret) || MyCommon.IsNullOrEmpty(username)) - { + if (credential is TwitterCredentialNone) Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid; - } + this.ResetApiStatus(); - this.Api.Initialize(appToken, token, tokenSecret, userId, username); + this.Api.Initialize(credential, userId, username); } public async Task PostStatus(PostStatusParams param) From f2c5fb9fe850ce87c167e33a2de18e07625ac56f Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 22:44:49 +0900 Subject: [PATCH 46/76] =?UTF-8?q?TwitterApi=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E5=88=9D=E6=9C=9F=E5=80=A4=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?TwitterCredentialNone=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 125 +++++++++++++------------- OpenTween.Tests/MediaSelectorTest.cs | 54 ++++------- OpenTween.Tests/TweenMainTest.cs | 1 - OpenTween.Tests/TwitterTest.cs | 14 +-- OpenTween/Api/TwitterApi.cs | 20 +---- OpenTween/Twitter.cs | 11 --- 6 files changed, 89 insertions(+), 136 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index bac959bb4..1fde2e592 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -51,33 +51,28 @@ private void MyCommonSetup() [Fact] public void Initialize_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); - Assert.Null(twitterApi.ApiConnection); + using var twitterApi = new TwitterApi(); + var apiConnection = Assert.IsType(twitterApi.Connection); + Assert.IsType(apiConnection.Credential); - twitterApi.Initialize("*** AccessToken ***", "*** AccessSecret ***", userId: 100L, screenName: "hogehoge"); + var credential = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken ***", "*** AccessSecret ***"); + twitterApi.Initialize(credential, userId: 100L, screenName: "hogehoge"); - Assert.IsType(twitterApi.ApiConnection); - - var apiConnection = (TwitterApiConnection)twitterApi.ApiConnection!; - var credential = Assert.IsType(apiConnection.Credential); - Assert.Equal("*** AccessToken ***", credential.Token); - Assert.Equal("*** AccessSecret ***", credential.TokenSecret); + apiConnection = Assert.IsType(twitterApi.Connection); + Assert.Same(credential, apiConnection.Credential); Assert.Equal(100L, twitterApi.CurrentUserId); Assert.Equal("hogehoge", twitterApi.CurrentScreenName); // 複数回 Initialize を実行した場合は新たに TwitterApiConnection が生成される - twitterApi.Initialize("*** AccessToken2 ***", "*** AccessSecret2 ***", userId: 200L, screenName: "foobar"); + var credential2 = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken2 ***", "*** AccessSecret2 ***"); + twitterApi.Initialize(credential2, userId: 200L, screenName: "foobar"); var oldApiConnection = apiConnection; Assert.True(oldApiConnection.IsDisposed); - Assert.IsType(twitterApi.ApiConnection); - - apiConnection = (TwitterApiConnection)twitterApi.ApiConnection!; - credential = Assert.IsType(apiConnection.Credential); - Assert.Equal("*** AccessToken2 ***", credential.Token); - Assert.Equal("*** AccessSecret2 ***", credential.TokenSecret); + apiConnection = Assert.IsType(twitterApi.Connection); + Assert.Same(credential2, apiConnection.Credential); Assert.Equal(200L, twitterApi.CurrentUserId); Assert.Equal("foobar", twitterApi.CurrentScreenName); @@ -103,7 +98,7 @@ public async Task StatusesHomeTimeline_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesHomeTimeline(200, maxId: new("900"), sinceId: new("100")); @@ -131,7 +126,7 @@ public async Task StatusesMentionsTimeline_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesMentionsTimeline(200, maxId: new("900"), sinceId: new("100")); @@ -161,7 +156,7 @@ public async Task StatusesUserTimeline_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesUserTimeline("twitterapi", count: 200, maxId: new("900"), sinceId: new("100")); @@ -187,7 +182,7 @@ public async Task StatusesShow_Test() ) .ReturnsAsync(new TwitterStatus { Id = 100L }); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesShow(statusId: new("100")); @@ -214,7 +209,7 @@ public async Task StatusesLookup_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesLookup(statusIds: new[] { "100", "200" }); @@ -244,7 +239,7 @@ public async Task StatusesUpdate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesUpdate( @@ -278,7 +273,7 @@ public async Task StatusesUpdate_ExcludeReplyUserIdsEmptyTest() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, excludeReplyUserIds: Array.Empty()) @@ -298,7 +293,7 @@ public async Task StatusesDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesDestroy(statusId: new("100")) @@ -324,7 +319,7 @@ public async Task StatusesRetweet_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.StatusesRetweet(new("100")) @@ -356,7 +351,7 @@ public async Task SearchTweets_Test() ) .ReturnsAsync(new TwitterSearchResult()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.SearchTweets("from:twitterapi", "en", count: 200, maxId: new("900"), sinceId: new("100")); @@ -381,7 +376,7 @@ public async Task ListsOwnerships_Test() ) .ReturnsAsync(new TwitterLists()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsOwnerships("twitterapi", cursor: -1L, count: 100); @@ -406,7 +401,7 @@ public async Task ListsSubscriptions_Test() ) .ReturnsAsync(new TwitterLists()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsSubscriptions("twitterapi", cursor: -1L, count: 100); @@ -432,7 +427,7 @@ public async Task ListsMemberships_Test() ) .ReturnsAsync(new TwitterLists()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMemberships("twitterapi", cursor: -1L, count: 100, filterToOwnedLists: true); @@ -456,7 +451,7 @@ public async Task ListsCreate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterList())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true) @@ -482,7 +477,7 @@ public async Task ListsUpdate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterList())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @private: true) @@ -505,7 +500,7 @@ public async Task ListsDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterList())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsDestroy(12345L) @@ -536,7 +531,7 @@ public async Task ListsStatuses_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsStatuses(12345L, count: 200, maxId: new("900"), sinceId: new("100"), includeRTs: true); @@ -563,7 +558,7 @@ public async Task ListsMembers_Test() ) .ReturnsAsync(new TwitterUsers()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembers(12345L, cursor: -1); @@ -590,7 +585,7 @@ public async Task ListsMembersShow_Test() ) .ReturnsAsync(new TwitterUser()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembersShow(12345L, "twitterapi"); @@ -616,7 +611,7 @@ public async Task ListsMembersCreate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembersCreate(12345L, "twitterapi") @@ -643,7 +638,7 @@ public async Task ListsMembersDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ListsMembersDestroy(12345L, "twitterapi") @@ -668,7 +663,7 @@ public async Task DirectMessagesEventsList_Test() ) .ReturnsAsync(new TwitterMessageEventList()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.DirectMessagesEventsList(count: 50, cursor: "12345abcdefg"); @@ -708,7 +703,7 @@ public async Task DirectMessagesEventsNew_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterMessageEventSingle())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.DirectMessagesEventsNew(recipientId: 12345L, text: "hogehoge", mediaId: 67890L); @@ -726,7 +721,7 @@ public async Task DirectMessagesEventsDestroy_Test() ) .Returns(Task.CompletedTask); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.DirectMessagesEventsDestroy(eventId: new("100")); @@ -752,7 +747,7 @@ public async Task UsersShow_Test() ) .ReturnsAsync(new TwitterUser { ScreenName = "twitterapi" }); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.UsersShow(screenName: "twitterapi"); @@ -778,7 +773,7 @@ public async Task UsersLookup_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.UsersLookup(userIds: new[] { "11111", "22222" }); @@ -801,7 +796,7 @@ public async Task UsersReportSpam_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser { ScreenName = "twitterapi" })); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.UsersReportSpam(screenName: "twitterapi") @@ -830,7 +825,7 @@ public async Task FavoritesList_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FavoritesList(200, maxId: 900L, sinceId: 100L); @@ -853,7 +848,7 @@ public async Task FavoritesCreate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FavoritesCreate(statusId: new("100")) @@ -877,7 +872,7 @@ public async Task FavoritesDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FavoritesDestroy(statusId: new("100")) @@ -898,7 +893,7 @@ public async Task FriendshipsShow_Test() ) .ReturnsAsync(new TwitterFriendship()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FriendshipsShow(sourceScreenName: "twitter", targetScreenName: "twitterapi"); @@ -917,7 +912,7 @@ public async Task FriendshipsCreate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterFriendship())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FriendshipsCreate(screenName: "twitterapi") @@ -937,7 +932,7 @@ public async Task FriendshipsDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterFriendship())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FriendshipsDestroy(screenName: "twitterapi") @@ -958,7 +953,7 @@ public async Task NoRetweetIds_Test() ) .ReturnsAsync(Array.Empty()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.NoRetweetIds(); @@ -978,7 +973,7 @@ public async Task FollowersIds_Test() ) .ReturnsAsync(new TwitterIds()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.FollowersIds(cursor: -1L); @@ -998,7 +993,7 @@ public async Task MutesUsersIds_Test() ) .ReturnsAsync(new TwitterIds()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MutesUsersIds(cursor: -1L); @@ -1018,7 +1013,7 @@ public async Task BlocksIds_Test() ) .ReturnsAsync(new TwitterIds()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.BlocksIds(cursor: -1L); @@ -1041,7 +1036,7 @@ public async Task BlocksCreate_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.BlocksCreate(screenName: "twitterapi") @@ -1065,7 +1060,7 @@ public async Task BlocksDestroy_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.BlocksDestroy(screenName: "twitterapi") @@ -1095,7 +1090,7 @@ public async Task AccountVerifyCredentials_Test() ScreenName = "opentween", }); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.AccountVerifyCredentials(); @@ -1126,7 +1121,7 @@ public async Task AccountUpdateProfile_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", location: "Location", description: "") @@ -1154,7 +1149,7 @@ public async Task AccountUpdateProfileImage_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUser())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.AccountUpdateProfileImage(media) @@ -1175,7 +1170,7 @@ public async Task ApplicationRateLimitStatus_Test() ) .ReturnsAsync(new TwitterRateLimits()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.ApplicationRateLimitStatus(); @@ -1195,7 +1190,7 @@ public async Task Configuration_Test() ) .ReturnsAsync(new TwitterConfiguration()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.Configuration(); @@ -1220,7 +1215,7 @@ public async Task MediaUploadInit_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaInit())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", mediaCategory: "dm_image") @@ -1248,7 +1243,7 @@ public async Task MediaUploadAppend_Test() ) .Returns(Task.CompletedTask); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadAppend(mediaId: 11111L, segmentIndex: 1, media: media); @@ -1271,7 +1266,7 @@ public async Task MediaUploadFinalize_Test() ) .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaResult())); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadFinalize(mediaId: 11111L) @@ -1296,7 +1291,7 @@ public async Task MediaUploadStatus_Test() ) .ReturnsAsync(new TwitterUploadMediaResult()); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MediaUploadStatus(mediaId: 11111L); @@ -1315,7 +1310,7 @@ public async Task MediaMetadataCreate_Test() ) .ReturnsAsync(""); - using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret")); + using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; await twitterApi.MediaMetadataCreate(mediaId: 12345L, altText: "hogehoge"); diff --git a/OpenTween.Tests/MediaSelectorTest.cs b/OpenTween.Tests/MediaSelectorTest.cs index 2ad1e83f3..ffd650269 100644 --- a/OpenTween.Tests/MediaSelectorTest.cs +++ b/OpenTween.Tests/MediaSelectorTest.cs @@ -51,10 +51,9 @@ private void MyCommonSetup() [Fact] public void SelectedMediaServiceIndex_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); Assert.Equal("Twitter", mediaSelector.MediaServices[0].Key); @@ -70,10 +69,9 @@ public void SelectedMediaServiceIndex_Test() [Fact] public void SelectMediaService_TwitterTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -89,10 +87,9 @@ public void SelectMediaService_TwitterTest() [Fact] public void SelectMediaService_ImgurTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Imgur"); @@ -106,10 +103,9 @@ public void SelectMediaService_ImgurTest() [Fact] public void AddMediaItem_FilePath_SingleTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -132,10 +128,9 @@ public void AddMediaItem_FilePath_SingleTest() [Fact] public void AddMediaItem_MemoryImageTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -160,10 +155,9 @@ public void AddMediaItem_MemoryImageTest() [Fact] public void AddMediaItem_FilePath_MultipleTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -186,10 +180,9 @@ public void AddMediaItem_FilePath_MultipleTest() [Fact] public void ClearMediaItems_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -207,10 +200,9 @@ public void ClearMediaItems_Test() [Fact] public void DetachMediaItems_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -230,10 +222,9 @@ public void DetachMediaItems_Test() [Fact] public void SelectedMediaItemChange_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -268,10 +259,9 @@ public void SelectedMediaItemChange_Test() [Fact] public void SelectedMediaItemChange_DisposeTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -292,10 +282,9 @@ public void SelectedMediaItemChange_DisposeTest() [Fact] public void SetSelectedMediaAltText_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -317,10 +306,9 @@ public void SetSelectedMediaAltText_Test() [Fact] public void Validate_PassTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -333,10 +321,9 @@ public void Validate_PassTest() [Fact] public void Validate_EmptyErrorTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -350,10 +337,9 @@ public void Validate_EmptyErrorTest() [Fact] public void Validate_ServiceNotSelectedErrorTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); using var mediaItem = TestUtils.CreateDummyMediaItem(); @@ -368,10 +354,9 @@ public void Validate_ServiceNotSelectedErrorTest() [Fact] public void Validate_ExtensionErrorTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -391,10 +376,9 @@ public void Validate_ExtensionErrorTest() [Fact] public void Validate_FileSizeErrorTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); - twitter.Initialize("", "", "", 0L); mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); mediaSelector.SelectMediaService("Twitter"); @@ -414,7 +398,7 @@ public void Validate_FileSizeErrorTest() [Fact] public void MoveSelectedMediaItemToPrevious_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); @@ -430,7 +414,7 @@ public void MoveSelectedMediaItemToPrevious_Test() [Fact] public void MoveSelectedMediaItemToNext_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); @@ -446,7 +430,7 @@ public void MoveSelectedMediaItemToNext_Test() [Fact] public void RemoveSelectedMediaItem_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); using var mediaSelector = new MediaSelector(); diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index 87dfbec72..ea14617c6 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -48,7 +48,6 @@ public void Initialize_Test() using var imageCache = new ImageCache(); using var iconAssets = new IconAssetsManager(); var thumbnailGenerator = new ThumbnailGenerator(new(autoupdate: false)); - twitter.Initialize(new TwitterCredentialNone(), "", 0L); using var tweenMain = new TweenMain(settings, tabinfo, twitter, imageCache, iconAssets, thumbnailGenerator); } diff --git a/OpenTween.Tests/TwitterTest.cs b/OpenTween.Tests/TwitterTest.cs index 56bdf6c52..89d6b784d 100644 --- a/OpenTween.Tests/TwitterTest.cs +++ b/OpenTween.Tests/TwitterTest.cs @@ -224,7 +224,7 @@ public void GetApiResultCount_AdditionalCountTest() [Fact] public void GetTextLengthRemain_Test() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); Assert.Equal(280, twitter.GetTextLengthRemain("")); @@ -234,7 +234,7 @@ public void GetTextLengthRemain_Test() [Fact] public void GetTextLengthRemain_DirectMessageTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); // 2015年8月から DM の文字数上限が 10,000 文字に変更された @@ -255,7 +255,7 @@ public void GetTextLengthRemain_DirectMessageTest() [Fact] public void GetTextLengthRemain_UrlTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); // t.co に短縮される分の文字数を考慮 @@ -272,7 +272,7 @@ public void GetTextLengthRemain_UrlTest() [Fact] public void GetTextLengthRemain_UrlWithoutSchemeTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); // t.co に短縮される分の文字数を考慮 @@ -290,7 +290,7 @@ public void GetTextLengthRemain_UrlWithoutSchemeTest() [Fact] public void GetTextLengthRemain_SurrogatePairTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); Assert.Equal(278, twitter.GetTextLengthRemain("🍣")); @@ -300,7 +300,7 @@ public void GetTextLengthRemain_SurrogatePairTest() [Fact] public void GetTextLengthRemain_EmojiTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); // 絵文字の文字数カウントの仕様変更に対するテストケース @@ -320,7 +320,7 @@ public void GetTextLengthRemain_EmojiTest() [Fact] public void GetTextLengthRemain_BrokenSurrogateTest() { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitterApi = new TwitterApi(); using var twitter = new Twitter(twitterApi); // 投稿欄に IME から絵文字を入力すると HighSurrogate のみ入力された状態で TextChanged イベントが呼ばれることがある diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index bc97384de..c10c2ae1f 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -41,28 +41,14 @@ public sealed class TwitterApi : IDisposable public string CurrentScreenName { get; private set; } = ""; - public IApiConnectionLegacy Connection => this.ApiConnection ?? throw new InvalidOperationException(); + public IApiConnectionLegacy Connection => this.ApiConnection; - internal IApiConnectionLegacy? ApiConnection; + internal IApiConnectionLegacy ApiConnection; public TwitterAppToken AppToken { get; private set; } = TwitterAppToken.GetDefault(); public TwitterApi() - { - } - - public TwitterApi(ApiKey consumerKey, ApiKey consumerSecret) - { - this.AppToken = new() - { - AuthType = APIAuthType.OAuth1, - OAuth1CustomConsumerKey = consumerKey, - OAuth1CustomConsumerSecret = consumerSecret, - }; - } - - public void Initialize(string accessToken, string accessSecret, long userId, string screenName) - => this.Initialize(new TwitterCredentialOAuth1(this.AppToken, accessToken, accessSecret), userId, screenName); + => this.ApiConnection = new TwitterApiConnection(new TwitterCredentialNone()); public void Initialize(ITwitterCredential credential, long userId, string screenName) { diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 48be5ba98..facd008d0 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -217,17 +217,6 @@ public async Task VerifyCredentialsAsync() this.UpdateUserStats(user); } - public void Initialize(string token, string tokenSecret, string username, long userId) - { - // OAuth認証 - if (MyCommon.IsNullOrEmpty(token) || MyCommon.IsNullOrEmpty(tokenSecret) || MyCommon.IsNullOrEmpty(username)) - { - Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid; - } - this.ResetApiStatus(); - this.Api.Initialize(token, tokenSecret, userId, username); - } - public void Initialize(ITwitterCredential credential, string username, long userId) { // OAuth認証 From 043b57e72a9a085331afeee5acb06acf3b5a52d8 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 10 Dec 2023 22:55:36 +0900 Subject: [PATCH 47/76] =?UTF-8?q?ITwitterCredential.AuthType=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Api/TwitterApi.cs | 4 ++-- OpenTween/Connection/ITwitterCredential.cs | 2 +- .../Connection/TwitterCredentialCookie.cs | 3 +++ OpenTween/Connection/TwitterCredentialNone.cs | 4 ++-- .../Connection/TwitterCredentialOAuth1.cs | 3 +++ OpenTween/Setting/SettingCommon.cs | 1 + OpenTween/Tween.cs | 4 ++-- OpenTween/Twitter.cs | 18 +++++++++--------- 8 files changed, 23 insertions(+), 16 deletions(-) diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index c10c2ae1f..6614555e7 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -45,14 +45,14 @@ public sealed class TwitterApi : IDisposable internal IApiConnectionLegacy ApiConnection; - public TwitterAppToken AppToken { get; private set; } = TwitterAppToken.GetDefault(); + public APIAuthType AuthType { get; private set; } = APIAuthType.None; public TwitterApi() => this.ApiConnection = new TwitterApiConnection(new TwitterCredentialNone()); public void Initialize(ITwitterCredential credential, long userId, string screenName) { - this.AppToken = credential.AppToken; + this.AuthType = credential.AuthType; var newInstance = new TwitterApiConnection(credential); var oldInstance = Interlocked.Exchange(ref this.ApiConnection, newInstance); diff --git a/OpenTween/Connection/ITwitterCredential.cs b/OpenTween/Connection/ITwitterCredential.cs index 87f5a8dc8..fdad97079 100644 --- a/OpenTween/Connection/ITwitterCredential.cs +++ b/OpenTween/Connection/ITwitterCredential.cs @@ -27,7 +27,7 @@ namespace OpenTween.Connection { public interface ITwitterCredential { - TwitterAppToken AppToken { get; } + APIAuthType AuthType { get; } HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler); } diff --git a/OpenTween/Connection/TwitterCredentialCookie.cs b/OpenTween/Connection/TwitterCredentialCookie.cs index 14452e096..ad0387cbc 100644 --- a/OpenTween/Connection/TwitterCredentialCookie.cs +++ b/OpenTween/Connection/TwitterCredentialCookie.cs @@ -27,6 +27,9 @@ namespace OpenTween.Connection { public class TwitterCredentialCookie : ITwitterCredential { + public APIAuthType AuthType + => APIAuthType.TwitterComCookie; + public TwitterAppToken AppToken { get; } public TwitterCredentialCookie(TwitterAppToken appToken) diff --git a/OpenTween/Connection/TwitterCredentialNone.cs b/OpenTween/Connection/TwitterCredentialNone.cs index 67d1c86be..f061203f0 100644 --- a/OpenTween/Connection/TwitterCredentialNone.cs +++ b/OpenTween/Connection/TwitterCredentialNone.cs @@ -27,8 +27,8 @@ namespace OpenTween.Connection { public class TwitterCredentialNone : ITwitterCredential { - public TwitterAppToken AppToken - => TwitterAppToken.GetDefault(); + public APIAuthType AuthType + => APIAuthType.None; public HttpMessageHandler CreateHttpHandler(HttpMessageHandler innerHandler) => innerHandler; diff --git a/OpenTween/Connection/TwitterCredentialOAuth1.cs b/OpenTween/Connection/TwitterCredentialOAuth1.cs index 298ace003..9d2d15961 100644 --- a/OpenTween/Connection/TwitterCredentialOAuth1.cs +++ b/OpenTween/Connection/TwitterCredentialOAuth1.cs @@ -27,6 +27,9 @@ namespace OpenTween.Connection { public class TwitterCredentialOAuth1 : ITwitterCredential { + public APIAuthType AuthType + => APIAuthType.OAuth1; + public TwitterAppToken AppToken { get; } public string Token { get; } diff --git a/OpenTween/Setting/SettingCommon.cs b/OpenTween/Setting/SettingCommon.cs index 5d9f3ad7b..488d926b2 100644 --- a/OpenTween/Setting/SettingCommon.cs +++ b/OpenTween/Setting/SettingCommon.cs @@ -455,6 +455,7 @@ public override string ToString() public enum APIAuthType { + None, OAuth1, TwitterComCookie, } diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 0dc356877..0d2de5d64 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -1212,7 +1212,7 @@ private async void PostButton_Click(object sender, EventArgs e) var status = new PostStatusParams(); var statusTextCompat = this.FormatStatusText(this.StatusText.Text); - if (this.GetRestStatusCount(statusTextCompat) >= 0 && this.tw.Api.AppToken.AuthType == APIAuthType.OAuth1) + if (this.GetRestStatusCount(statusTextCompat) >= 0 && this.tw.Api.AuthType == APIAuthType.OAuth1) { // auto_populate_reply_metadata や attachment_url を使用しなくても 140 字以内に // 収まる場合はこれらのオプションを使用せずに投稿する @@ -7047,7 +7047,7 @@ private void SetApiStatusLabel(string? endpointName = null) if (endpointName == null) { - var authByCookie = this.tw.Api.AppToken.AuthType == APIAuthType.TwitterComCookie; + var authByCookie = this.tw.Api.AuthType == APIAuthType.TwitterComCookie; // 表示中のタブに応じて更新 endpointName = tabType switch diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index facd008d0..3b473a10a 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -242,7 +242,7 @@ await this.SendDirectMessage(param.Text, mediaId) TwitterStatus status; - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new CreateTweetRequest { @@ -287,7 +287,7 @@ await this.SendDirectMessage(param.Text, mediaId) public async Task DeleteTweet(TwitterStatusId tweetId) { - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new DeleteTweetRequest { @@ -393,7 +393,7 @@ await this.CreateDirectMessagesEventFromJson(messageEventSingle, read: true) var target = post.RetweetedId ?? id; // 再RTの場合は元発言をRT - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new CreateRetweetRequest { @@ -435,7 +435,7 @@ public async Task DeleteRetweet(PostClass post) if (post.RetweetedId == null) throw new ArgumentException("post is not retweeted status", nameof(post)); - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new DeleteRetweetRequest { @@ -452,7 +452,7 @@ await this.Api.StatusesDestroy(post.StatusId.ToTwitterStatusId()) public async Task GetUserInfo(string screenName) { - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new UserByScreenNameRequest { @@ -655,7 +655,7 @@ public async Task GetUserTimelineApi(bool read, UserTimelineTabModel tab, bool m var count = GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, more, false); TwitterStatus[] statuses; - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var userId = tab.UserId; if (MyCommon.IsNullOrEmpty(userId)) @@ -711,7 +711,7 @@ public async Task GetStatusApi(bool read, TwitterStatusId id) this.CheckAccountState(); TwitterStatus status; - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new TweetDetailRequest { @@ -867,7 +867,7 @@ public async Task GetListStatus(bool read, ListTimelineTabModel tab, bool more, var count = GetApiResultCount(MyCommon.WORKERTYPE.List, more, startup); TwitterStatus[] statuses; - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new ListLatestTweetsTimelineRequest(tab.ListInfo.Id.ToString()) { @@ -1077,7 +1077,7 @@ public async Task GetSearch(bool read, PublicSearchTabModel tab, bool more) var count = GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, more, false); TwitterStatus[] statuses; - if (this.Api.AppToken.AuthType == APIAuthType.TwitterComCookie) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { var request = new SearchTimelineRequest(tab.SearchWords) { From 2c3b4cc7b8f2e4765deb0b2c3c632a69c4e7f05a Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 11 Dec 2023 23:19:10 +0900 Subject: [PATCH 48/76] =?UTF-8?q?=E7=99=BA=E8=A8=80=E3=81=AB=E5=90=AB?= =?UTF-8?q?=E3=81=BE=E3=82=8C=E3=82=8B=E7=9F=AD=E7=B8=AEURL=E3=81=AE?= =?UTF-8?q?=E5=B1=95=E9=96=8B=E5=87=A6=E7=90=86=E3=82=92PostUrlExpander?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Models/PostClassTest.cs | 59 ------------ OpenTween.Tests/Models/PostUrlExpanderTest.cs | 91 +++++++++++++++++++ .../Models/TwitterPostFactoryTest.cs | 3 - OpenTween/Models/PostClass.cs | 60 ++---------- OpenTween/Models/PostUrlExpander.cs | 65 +++++++++++++ OpenTween/Twitter.cs | 10 +- 6 files changed, 171 insertions(+), 117 deletions(-) create mode 100644 OpenTween.Tests/Models/PostUrlExpanderTest.cs create mode 100644 OpenTween/Models/PostUrlExpander.cs diff --git a/OpenTween.Tests/Models/PostClassTest.cs b/OpenTween.Tests/Models/PostClassTest.cs index f4f32a7c6..c7bbfb100 100644 --- a/OpenTween.Tests/Models/PostClassTest.cs +++ b/OpenTween.Tests/Models/PostClassTest.cs @@ -305,64 +305,5 @@ public void ConvertToOriginalPost_ErrorTest() Assert.Throws(() => post.ConvertToOriginalPost()); } - - private class FakeExpandedUrlInfo : PostClass.ExpandedUrlInfo - { - public TaskCompletionSource FakeResult = new(); - - public FakeExpandedUrlInfo(string url, string expandedUrl, bool deepExpand) - : base(url, expandedUrl, deepExpand) - { - } - - protected override async Task DeepExpandAsync() - => this.expandedUrl = await this.FakeResult.Task; - } - - [Fact] - public async Task ExpandedUrls_BasicScenario() - { - PostClass.ExpandedUrlInfo.AutoExpand = true; - - var post = new PostClass - { - Text = """bit.ly/abcde""", - ExpandedUrls = new[] - { - new FakeExpandedUrlInfo( - // 展開前の t.co ドメインの URL - url: "http://t.co/aaaaaaa", - - // Entity の expanded_url に含まれる URL - expandedUrl: "http://bit.ly/abcde", - - // expandedUrl をさらに ShortUrl クラスで再帰的に展開する - deepExpand: true - ), - }, - }; - - var urlInfo = (FakeExpandedUrlInfo)post.ExpandedUrls.Single(); - - // ExpandedUrlInfo による展開が完了していない状態 - // → この段階では Entity に含まれる expanded_url の URL が使用される - Assert.False(urlInfo.ExpandedCompleted); - Assert.Equal("http://bit.ly/abcde", urlInfo.ExpandedUrl); - Assert.Equal("http://bit.ly/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa")); - Assert.Equal(new[] { "http://bit.ly/abcde" }, post.GetExpandedUrls()); - Assert.Equal("""bit.ly/abcde""", post.Text); - - // bit.ly 展開後の URL は「http://example.com/abcde」 - urlInfo.FakeResult.SetResult("http://example.com/abcde"); - await urlInfo.ExpandTask; - - // ExpandedUrlInfo による展開が完了した後の状態 - // → 再帰的な展開後の URL が使用される - Assert.True(urlInfo.ExpandedCompleted); - Assert.Equal("http://example.com/abcde", urlInfo.ExpandedUrl); - Assert.Equal("http://example.com/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa")); - Assert.Equal(new[] { "http://example.com/abcde" }, post.GetExpandedUrls()); - Assert.Equal("""bit.ly/abcde""", post.Text); - } } } diff --git a/OpenTween.Tests/Models/PostUrlExpanderTest.cs b/OpenTween.Tests/Models/PostUrlExpanderTest.cs new file mode 100644 index 000000000..e6620600d --- /dev/null +++ b/OpenTween.Tests/Models/PostUrlExpanderTest.cs @@ -0,0 +1,91 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Models +{ + public class PostUrlExpanderTest + { + [Fact] + public async Task Expand_Test() + { + var handler = new HttpMessageHandlerMock(); + using var http = new HttpClient(handler); + var shortUrl = new ShortUrl(http); + + // https://bit.ly/abcde -> https://example.com/abcde + handler.Enqueue(x => + { + Assert.Equal(HttpMethod.Head, x.Method); + Assert.Equal(new Uri("https://bit.ly/abcde"), x.RequestUri); + + return new HttpResponseMessage(HttpStatusCode.TemporaryRedirect) + { + Headers = { Location = new Uri("https://example.com/abcde") }, + }; + }); + + var post = new PostClass + { + Text = """bit.ly/abcde""", + ExpandedUrls = new[] + { + new PostClass.ExpandedUrlInfo( + // 展開前の t.co ドメインの URL + Url: "https://t.co/aaaaaaa", + + // Entity の expanded_url に含まれる URL + ExpandedUrl: "https://bit.ly/abcde" + ), + }, + }; + + var urlInfo = post.ExpandedUrls.Single(); + + // 短縮 URL の展開が完了していない状態 + // → この段階では Entity に含まれる expanded_url の URL が使用される + Assert.False(urlInfo.ExpandCompleted); + Assert.Equal("https://bit.ly/abcde", urlInfo.ExpandedUrl); + Assert.Equal("https://bit.ly/abcde", post.GetExpandedUrl("https://t.co/aaaaaaa")); + Assert.Equal(new[] { "https://bit.ly/abcde" }, post.GetExpandedUrls()); + Assert.Equal("""bit.ly/abcde""", post.Text); + + // bit.ly 展開後の URL は「https://example.com/abcde」 + var expander = new PostUrlExpander(shortUrl); + await expander.Expand(post); + + // 短縮 URL の展開が完了した後の状態 + // → 再帰的な展開後の URL が使用される + urlInfo = post.ExpandedUrls.Single(); + Assert.True(urlInfo.ExpandCompleted); + Assert.Equal("https://example.com/abcde", urlInfo.ExpandedUrl); + Assert.Equal("https://example.com/abcde", post.GetExpandedUrl("https://t.co/aaaaaaa")); + Assert.Equal(new[] { "https://example.com/abcde" }, post.GetExpandedUrls()); + Assert.Equal("""bit.ly/abcde""", post.Text); + } + } +} diff --git a/OpenTween.Tests/Models/TwitterPostFactoryTest.cs b/OpenTween.Tests/Models/TwitterPostFactoryTest.cs index c12a7d044..88718fd24 100644 --- a/OpenTween.Tests/Models/TwitterPostFactoryTest.cs +++ b/OpenTween.Tests/Models/TwitterPostFactoryTest.cs @@ -32,9 +32,6 @@ public class TwitterPostFactoryTest private readonly Random random = new(); - public TwitterPostFactoryTest() - => PostClass.ExpandedUrlInfo.AutoExpand = false; - private TabInformations CreateTabinfo() { var tabinfo = new TabInformations(); diff --git a/OpenTween/Models/PostClass.cs b/OpenTween/Models/PostClass.cs index d73455fde..b1791c80c 100644 --- a/OpenTween/Models/PostClass.cs +++ b/OpenTween/Models/PostClass.cs @@ -117,60 +117,12 @@ public string Text public bool IsPromoted { get; set; } - /// - /// に含まれる t.co の展開後の URL を保持するクラス - /// - public class ExpandedUrlInfo : ICloneable + public record ExpandedUrlInfo( + string Url, + string ExpandedUrl + ) { - public static bool AutoExpand { get; set; } = true; - - /// 展開前の t.co ドメインの URL - public string Url { get; } - - /// 展開後の URL - /// - /// による展開が完了するまでは Entity に含まれる expanded_url の値を返します - /// - public string ExpandedUrl => this.expandedUrl; - - /// による展開を行うタスク - public Task ExpandTask { get; private set; } - - /// による展開が完了したか否か - public bool ExpandedCompleted => this.ExpandTask.IsCompleted; - - protected string expandedUrl; - - public ExpandedUrlInfo(string url, string expandedUrl) - : this(url, expandedUrl, deepExpand: true) - { - } - - public ExpandedUrlInfo(string url, string expandedUrl, bool deepExpand) - { - this.Url = url; - this.expandedUrl = expandedUrl; - - if (AutoExpand && deepExpand) - this.ExpandTask = this.DeepExpandAsync(); - else - this.ExpandTask = Task.CompletedTask; - } - - protected virtual async Task DeepExpandAsync() - { - var origUrl = this.expandedUrl; - var newUrl = await ShortUrl.Instance.ExpandUrlAsync(origUrl) - .ConfigureAwait(false); - - Interlocked.CompareExchange(ref this.expandedUrl, newUrl, origUrl); - } - - public ExpandedUrlInfo Clone() - => new(this.Url, this.ExpandedUrl, deepExpand: false); - - object ICloneable.Clone() - => this.Clone(); + public bool ExpandCompleted { get; init; } } [Flags] @@ -333,7 +285,7 @@ private string ReplaceToExpandedUrl(string html, out bool completedAll) foreach (var urlInfo in this.ExpandedUrls) { - if (!urlInfo.ExpandedCompleted) + if (!urlInfo.ExpandCompleted) completedAll = false; var tcoUrl = urlInfo.Url; diff --git a/OpenTween/Models/PostUrlExpander.cs b/OpenTween/Models/PostUrlExpander.cs new file mode 100644 index 000000000..7ee9a208f --- /dev/null +++ b/OpenTween/Models/PostUrlExpander.cs @@ -0,0 +1,65 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenTween.Models +{ + public class PostUrlExpander + { + private readonly ShortUrl shortUrl; + + public PostUrlExpander(ShortUrl shortUrl) + => this.shortUrl = shortUrl; + + public async Task Expand(PostClass post) + { + var urls = post.ExpandedUrls; + if (urls.Length == 0) + return; + + var tasks = MyCommon.CountUp(0, urls.Length - 1) + .Select(i => this.UpdateUrlItem(urls, i)); + + await Task.WhenAll(tasks); + } + + public async Task UpdateUrlItem(PostClass.ExpandedUrlInfo[] urls, int index) + { + var urlItem = urls[index]; + + var expandedUrl = await this.shortUrl.ExpandUrlAsync(urlItem.ExpandedUrl) + .ConfigureAwait(false); + + var newUrlItem = urlItem with + { + ExpandedUrl = expandedUrl, + ExpandCompleted = true, + }; + Interlocked.Exchange(ref urls[index], newUrlItem); + } + } +} diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 3b473a10a..ba09b07b8 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -173,12 +173,14 @@ public class Twitter : IDisposable private long[] noRTId = Array.Empty(); private readonly TwitterPostFactory postFactory; + private readonly PostUrlExpander urlExpander; private string? previousStatusId = null; public Twitter(TwitterApi api) { this.postFactory = new(TabInformations.GetInstance()); + this.urlExpander = new(ShortUrl.Instance); this.Api = api; this.Configuration = TwitterConfiguration.DefaultConfiguration(); @@ -752,7 +754,12 @@ private PostClass CreatePostsFromStatusData(TwitterStatus status) => this.CreatePostsFromStatusData(status, favTweet: false); private PostClass CreatePostsFromStatusData(TwitterStatus status, bool favTweet) - => this.postFactory.CreateFromStatus(status, this.UserId, this.followerId, favTweet); + { + var post = this.postFactory.CreateFromStatus(status, this.UserId, this.followerId, favTweet); + _ = this.urlExpander.Expand(post); + + return post; + } private PostId? CreatePostsFromJson(TwitterStatus[] items, MyCommon.WORKERTYPE gType, TabModel? tab, bool read) { @@ -1195,6 +1202,7 @@ private void CreateDirectMessagesEventFromJson( foreach (var eventItem in events) { var post = this.postFactory.CreateFromDirectMessageEvent(eventItem, users, apps, this.UserId); + _ = this.urlExpander.Expand(post); post.IsRead = read; if (post.IsMe && !read && this.ReadOwnPost) From e016aeaeb2e73ecdd057df529657edb386c89826 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 11 Dec 2023 23:53:17 +0900 Subject: [PATCH 49/76] =?UTF-8?q?OpenTween.Tests/Resources=E4=BB=A5?= =?UTF-8?q?=E4=B8=8B=E3=81=AB=E3=81=82=E3=82=8B=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=AECopyToOutputDirectory=E3=82=92=E4=B8=80?= =?UTF-8?q?=E6=8B=AC=E3=81=A7=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/OpenTween.Tests.csproj | 74 +------------------------- 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index dbaad23f1..6d5f37fd7 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -44,79 +44,7 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + PreserveNewest From cda0532adc31dc7d6032522fefad86065b3f5eac Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 00:27:29 +0900 Subject: [PATCH 50/76] =?UTF-8?q?TimelineTweet=E3=81=AB=E5=90=AB=E3=81=BE?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E5=BC=95=E7=94=A8=E3=83=84=E3=82=A4=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E5=8F=96=E5=BE=97=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + .../Api/GraphQL/TimelineTweetTest.cs | 28 ++ .../Responses/TimelineTweet_QuotedTweet.json | 252 ++++++++++++++++++ .../TimelineTweet_QuotedTweet_Tombstone.json | 159 +++++++++++ OpenTween/Api/GraphQL/TimelineTweet.cs | 17 +- 5 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet.json create mode 100644 OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet_Tombstone.json diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1b712a082..826fc3c39 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * NEW: graphqlエンドポイント経由で取得した引用ツイートの表示に対応 ==== Ver 3.9.0(2023/12/03) * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 diff --git a/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs b/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs index 6747ad158..f1958321e 100644 --- a/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs +++ b/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs @@ -139,6 +139,34 @@ public void ToStatus_WithTwitterPostFactory_SelfThread_Test() Assert.Equal(40480664L, post.UserId); } + [Fact] + public void ToStatus_WithTwitterPostFactory_QuotedTweet_Test() + { + var rootElm = this.LoadResponseDocument("TimelineTweet_QuotedTweet.json"); + var timelineTweet = new TimelineTweet(rootElm); + var status = timelineTweet.ToTwitterStatus(); + var postFactory = new TwitterPostFactory(this.CreateTabInfo()); + var post = postFactory.CreateFromStatus(status, selfUserId: 1L, new HashSet()); + + Assert.Equal("1588614645866147840", post.StatusId.Id); + var quotedPostId = Assert.Single(post.QuoteStatusIds); + Assert.Equal("1583108196868116480", quotedPostId.Id); + } + + [Fact] + public void ToStatus_WithTwitterPostFactory_QuotedTweet_Tombstone_Test() + { + var rootElm = this.LoadResponseDocument("TimelineTweet_QuotedTweet_Tombstone.json"); + var timelineTweet = new TimelineTweet(rootElm); + var status = timelineTweet.ToTwitterStatus(); + var postFactory = new TwitterPostFactory(this.CreateTabInfo()); + var post = postFactory.CreateFromStatus(status, selfUserId: 1L, new HashSet()); + + Assert.Equal("1614653321310253057", post.StatusId.Id); + var quotedPostId = Assert.Single(post.QuoteStatusIds); + Assert.Equal("1614650279194136576", quotedPostId.Id); + } + [Fact] public void ToStatus_WithTwitterPostFactory_PromotedTweet_Test() { diff --git a/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet.json b/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet.json new file mode 100644 index 000000000..67e4fe360 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet.json @@ -0,0 +1,252 @@ +{ + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1588614645866147840", + "has_birdwatch_notes": false, + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0MDQ4MDY2NA==", + "rest_id": "40480664", + "affiliates_highlighted_label": {}, + "has_graduated_access": false, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "can_dm": true, + "can_media_tag": true, + "created_at": "Sat May 16 15:20:01 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "m.upsilo.net/@upsilon", + "expanded_url": "https://m.upsilo.net/@upsilon", + "url": "https://t.co/vNMmyHHh15", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 215369, + "followers_count": 1287, + "friends_count": 1, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 92, + "location": "Funabashi, Chiba, Japan", + "media_count": 876, + "name": "upsilon", + "needs_phone_verification": false, + "normal_followers_count": 1287, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/40480664/1349188016", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_interstitial_type": "", + "screen_name": "kim_upsilon", + "statuses_count": 10081, + "translator_type": "regular", + "url": "https://t.co/vNMmyHHh15", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + } + } + } + }, + "unmention_data": {}, + "unified_card": { + "card_fetch_state": "NoCard" + }, + "edit_control": { + "edit_tweet_ids": [ + "1588614645866147840" + ], + "editable_until_msecs": "1667592021000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": true, + "views": { + "state": "Enabled" + }, + "source": "OpenTween (dev)", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1583108196868116480", + "has_birdwatch_notes": false, + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0MDQ4MDY2NA==", + "rest_id": "40480664", + "affiliates_highlighted_label": {}, + "has_graduated_access": false, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "can_dm": true, + "can_media_tag": true, + "created_at": "Sat May 16 15:20:01 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "m.upsilo.net/@upsilon", + "expanded_url": "https://m.upsilo.net/@upsilon", + "url": "https://t.co/vNMmyHHh15", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 215369, + "followers_count": 1287, + "friends_count": 1, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 92, + "location": "Funabashi, Chiba, Japan", + "media_count": 876, + "name": "upsilon", + "needs_phone_verification": false, + "normal_followers_count": 1287, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/40480664/1349188016", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_interstitial_type": "", + "screen_name": "kim_upsilon", + "statuses_count": 10081, + "translator_type": "regular", + "url": "https://t.co/vNMmyHHh15", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + } + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1583108196868116480" + ], + "editable_until_msecs": "1666279181000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": true, + "views": { + "state": "Enabled" + }, + "source": "OpenTween (dev)", + "legacy": { + "bookmark_count": 0, + "bookmarked": false, + "created_at": "Thu Oct 20 14:49:41 +0000 2022", + "conversation_id_str": "1583108196868116480", + "display_text_range": [ + 0, + 97 + ], + "entities": { + "user_mentions": [], + "urls": [], + "hashtags": [], + "symbols": [] + }, + "favorite_count": 2, + "favorited": false, + "full_text": "AppVeyorでビルドした時と自分の開発環境でビルドした時でなぜか sgen.exe の出力が異なって生成物のハッシュ値が一致しなくなる問題に悩み中。Reproducible Buildむずい", + "is_quote_status": false, + "lang": "ja", + "quote_count": 1, + "reply_count": 0, + "retweet_count": 0, + "retweeted": false, + "user_id_str": "40480664", + "id_str": "1583108196868116480" + } + } + }, + "legacy": { + "bookmark_count": 0, + "bookmarked": false, + "created_at": "Fri Nov 04 19:30:21 +0000 2022", + "conversation_id_str": "1588614645866147840", + "display_text_range": [ + 0, + 63 + ], + "entities": { + "user_mentions": [], + "urls": [ + { + "display_url": "twitter.com/kim_upsilon/st…", + "expanded_url": "https://twitter.com/kim_upsilon/status/1583108196868116480", + "url": "https://t.co/mb89Ecojqd", + "indices": [ + 40, + 63 + ] + } + ], + "hashtags": [], + "symbols": [] + }, + "favorite_count": 2, + "favorited": false, + "full_text": "これ結局原因が分からないまま sgen.exe を使うのを止めることで解決した https://t.co/mb89Ecojqd", + "is_quote_status": true, + "lang": "ja", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 0, + "quoted_status_id_str": "1583108196868116480", + "quoted_status_permalink": { + "url": "https://t.co/mb89Ecojqd", + "expanded": "https://twitter.com/kim_upsilon/status/1583108196868116480", + "display": "twitter.com/kim_upsilon/st…" + }, + "reply_count": 1, + "retweet_count": 0, + "retweeted": false, + "user_id_str": "40480664", + "id_str": "1588614645866147840" + }, + "quick_promote_eligibility": { + "eligibility": "IneligibleNotProfessional" + } + } + }, + "tweetDisplayType": "SelfThread", + "hasModeratedReplies": false +} diff --git a/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet_Tombstone.json b/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet_Tombstone.json new file mode 100644 index 000000000..d1de5794e --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/TimelineTweet_QuotedTweet_Tombstone.json @@ -0,0 +1,159 @@ +{ + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1614653321310253057", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0MDQ4MDY2NA==", + "rest_id": "40480664", + "affiliates_highlighted_label": {}, + "has_graduated_access": false, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "can_dm": false, + "can_media_tag": false, + "created_at": "Sat May 16 15:20:01 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "m.upsilo.net/@upsilon", + "expanded_url": "https://m.upsilo.net/@upsilon", + "url": "https://t.co/vNMmyHHh15", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 215391, + "followers_count": 1287, + "friends_count": 1, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 92, + "location": "Funabashi, Chiba, Japan", + "media_count": 876, + "name": "upsilon", + "normal_followers_count": 1287, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/40480664/1349188016", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_interstitial_type": "", + "screen_name": "kim_upsilon", + "statuses_count": 10081, + "translator_type": "regular", + "url": "https://t.co/vNMmyHHh15", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + } + } + } + }, + "unmention_data": {}, + "unified_card": { + "card_fetch_state": "NoCard" + }, + "edit_control": { + "edit_tweet_ids": [ + "1614653321310253057" + ], + "editable_until_msecs": "1673800125000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": true, + "views": { + "count": "1779", + "state": "EnabledWithCount" + }, + "source": "OpenTween (dev)", + "quoted_status_result": { + "result": { + "__typename": "TweetTombstone", + "tombstone": { + "__typename": "TextTombstone", + "text": { + "rtl": false, + "text": "This Post is from a suspended account. Learn more", + "entities": [ + { + "fromIndex": 39, + "toIndex": 49, + "ref": { + "type": "TimelineUrl", + "url": "https://help.twitter.com/rules-and-policies/notices-on-twitter", + "urlType": "ExternalUrl" + } + } + ] + } + } + } + }, + "legacy": { + "bookmark_count": 0, + "bookmarked": false, + "created_at": "Sun Jan 15 15:58:45 +0000 2023", + "conversation_id_str": "1614653321310253057", + "display_text_range": [ + 0, + 45 + ], + "entities": { + "user_mentions": [], + "urls": [ + { + "display_url": "twitter.com/omlll/status/1…", + "expanded_url": "https://twitter.com/omlll/status/1614650279194136576", + "url": "https://t.co/l1XzDghegz", + "indices": [ + 22, + 45 + ] + } + ], + "hashtags": [], + "symbols": [] + }, + "favorite_count": 9, + "favorited": false, + "full_text": "これは間違いなくバカが作ったツールですね…\nhttps://t.co/l1XzDghegz", + "is_quote_status": true, + "lang": "ja", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 0, + "quoted_status_id_str": "1614650279194136576", + "quoted_status_permalink": { + "url": "https://t.co/l1XzDghegz", + "expanded": "https://twitter.com/omlll/status/1614650279194136576", + "display": "twitter.com/omlll/status/1…" + }, + "reply_count": 1, + "retweet_count": 2, + "retweeted": false, + "user_id_str": "40480664", + "id_str": "1614653321310253057" + } + } + }, + "tweetDisplayType": "Tweet" +} diff --git a/OpenTween/Api/GraphQL/TimelineTweet.cs b/OpenTween/Api/GraphQL/TimelineTweet.cs index dd20e3401..af7b1764d 100644 --- a/OpenTween/Api/GraphQL/TimelineTweet.cs +++ b/OpenTween/Api/GraphQL/TimelineTweet.cs @@ -92,7 +92,7 @@ public void ThrowIfTweetIsTombstone() public static TwitterStatus ParseTweetUnion(XElement tweetUnionElm) { - var tweetElm = tweetUnionElm.Element("__typename")?.Value switch + var tweetElm = GetTweetTypeName(tweetUnionElm) switch { "Tweet" => tweetUnionElm, "TweetWithVisibilityResults" => tweetUnionElm.Element("tweet") ?? throw CreateParseError(), @@ -102,12 +102,18 @@ public static TwitterStatus ParseTweetUnion(XElement tweetUnionElm) return TimelineTweet.ParseTweet(tweetElm); } + public static string GetTweetTypeName(XElement tweetUnionElm) + => tweetUnionElm.Element("__typename")?.Value ?? throw CreateParseError(); + public static TwitterStatus ParseTweet(XElement tweetElm) { var tweetLegacyElm = tweetElm.Element("legacy") ?? throw CreateParseError(); var userElm = tweetElm.Element("core")?.Element("user_results")?.Element("result") ?? throw CreateParseError(); var retweetedTweetElm = tweetLegacyElm.Element("retweeted_status_result")?.Element("result"); var user = new TwitterGraphqlUser(userElm); + var quotedTweetElm = tweetElm.Element("quoted_status_result")?.Element("result") ?? null; + var quotedStatusPermalink = tweetLegacyElm.Element("quoted_status_permalink") ?? null; + var isQuotedTweetTombstone = quotedTweetElm != null && GetTweetTypeName(quotedTweetElm) == "TweetTombstone"; static string GetText(XElement elm, string name) => elm.Element(name)?.Value ?? throw CreateParseError(); @@ -168,6 +174,15 @@ static string GetText(XElement elm, string name) }, User = user.ToTwitterUser(), RetweetedStatus = retweetedTweetElm != null ? TimelineTweet.ParseTweetUnion(retweetedTweetElm) : null, + IsQuoteStatus = GetTextOrNull(tweetLegacyElm, "is_quote_status") == "true", + QuotedStatus = quotedTweetElm != null && !isQuotedTweetTombstone ? TimelineTweet.ParseTweetUnion(quotedTweetElm) : null, + QuotedStatusIdStr = GetTextOrNull(tweetLegacyElm, "quoted_status_id_str"), + QuotedStatusPermalink = quotedStatusPermalink == null ? null : new() + { + Url = GetText(quotedStatusPermalink, "url"), + Expanded = GetText(quotedStatusPermalink, "expanded"), + Display = GetText(quotedStatusPermalink, "display"), + }, }; } From 6bcc23bea1c5eabcbc383196887d6406fb5c7105 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 01:24:24 +0900 Subject: [PATCH 51/76] =?UTF-8?q?IApiConnectionLegacy.PostJsonAsync?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E7=AE=87=E6=89=80=E3=82=92PostJsonRequest=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 27 ++++++++++++++++--------- OpenTween/Api/TwitterApi.cs | 29 ++++++++++++++++++--------- OpenTween/Connection/ApiResponse.cs | 9 +++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 1fde2e592..87df7119d 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -23,6 +23,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; @@ -674,8 +675,9 @@ public async Task DirectMessagesEventsList_Test() [Fact] public async Task DirectMessagesEventsNew_Test() { + using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); var mock = new Mock(); - var responseText = """ + var requestJson = """ { "event": { "type": "message_create", @@ -697,11 +699,14 @@ public async Task DirectMessagesEventsNew_Test() } """; mock.Setup(x => - x.PostJsonAsync( - new Uri("direct_messages/events/new.json", UriKind.Relative), - responseText) + x.SendAsync( + It.Is(r => + r.RequestUri == new Uri("direct_messages/events/new.json", UriKind.Relative) && + r.JsonString == requestJson + ) + ) ) - .ReturnsAsync(LazyJson.Create(new TwitterMessageEventSingle())); + .ReturnsAsync(new ApiResponse(responseMessage)); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1304,11 +1309,13 @@ public async Task MediaMetadataCreate_Test() { var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync( - new Uri("https://upload.twitter.com/1.1/media/metadata/create.json", UriKind.Absolute), - """{"media_id": "12345", "alt_text": {"text": "hogehoge"}}""") - ) - .ReturnsAsync(""); + x.SendAsync( + It.Is(r => + r.RequestUri == new Uri("https://upload.twitter.com/1.1/media/metadata/create.json") && + r.JsonString == """{"media_id": "12345", "alt_text": {"text": "hogehoge"}}""" + ) + ) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 6614555e7..e3d3be4f0 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -425,10 +425,8 @@ public Task DirectMessagesEventsList(int? count = null, return this.Connection.GetAsync(endpoint, param, "/direct_messages/events/list"); } - public Task> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null) + public async Task> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null) { - var endpoint = new Uri("direct_messages/events/new.json", UriKind.Relative); - var attachment = ""; if (mediaId != null) { @@ -458,7 +456,16 @@ public Task> DirectMessagesEventsNew(long re } """; - return this.Connection.PostJsonAsync(endpoint, json); + var request = new PostJsonRequest + { + RequestUri = new("direct_messages/events/new.json", UriKind.Relative), + JsonString = json, + }; + + var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task DirectMessagesEventsDestroy(TwitterDirectMessageId eventId) @@ -792,14 +799,18 @@ public Task MediaUploadStatus(long mediaId) return this.Connection.GetAsync(endpoint, param, endpointName: null); } - public Task MediaMetadataCreate(long mediaId, string altText) + public async Task MediaMetadataCreate(long mediaId, string altText) { - var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json"); - var escapedAltText = JsonUtils.EscapeJsonString(altText); - var json = $$$"""{"media_id": "{{{mediaId}}}", "alt_text": {"text": "{{{escapedAltText}}}"}}"""; + var request = new PostJsonRequest + { + RequestUri = new("https://upload.twitter.com/1.1/media/metadata/create.json"), + JsonString = $$$"""{"media_id": "{{{mediaId}}}", "alt_text": {"text": "{{{escapedAltText}}}"}}""", + }; - return this.Connection.PostJsonAsync(endpoint, json); + await this.Connection.SendAsync(request) + .IgnoreResponse() + .ConfigureAwait(false); } public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null) diff --git a/OpenTween/Connection/ApiResponse.cs b/OpenTween/Connection/ApiResponse.cs index ee1560058..da03230e9 100644 --- a/OpenTween/Connection/ApiResponse.cs +++ b/OpenTween/Connection/ApiResponse.cs @@ -114,4 +114,13 @@ public async Task ReadAsString() .ConfigureAwait(false); } } + + public static class ApiResponseTaskExtension + { + public static async Task IgnoreResponse(this Task task) + { + using var response = await task.ConfigureAwait(false); + // レスポンスボディを読み込まず破棄する + } + } } From 666676ab362b431014b9a11e1e41f5d1d95555b2 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 01:27:32 +0900 Subject: [PATCH 52/76] =?UTF-8?q?TwitterApiConnection.PostJsonAsync?= =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 69 ------------------- OpenTween/Connection/IApiConnectionLegacy.cs | 4 -- OpenTween/Connection/TwitterApiConnection.cs | 29 -------- 3 files changed, 102 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index c646f07bb..b793548e3 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -481,75 +481,6 @@ public async Task PostLazyAsync_Multipart_NullTest() Assert.Equal(0, mockHandler.QueueCount); } - [Fact] - public async Task PostJsonAsync_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(async x => - { - Assert.Equal(HttpMethod.Post, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString()); - - var body = await x.Content.ReadAsStringAsync(); - - Assert.Equal("""{"aaaa": 1111}""", body); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(@"{""ok"":true}"), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - - var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}"""); - - Assert.Equal(@"{""ok"":true}", response); - Assert.Equal(0, mockHandler.QueueCount); - } - - [Fact] - public async Task PostJsonAsync_T_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(async x => - { - Assert.Equal(HttpMethod.Post, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString()); - - var body = await x.Content.ReadAsStringAsync(); - - Assert.Equal("""{"aaaa": 1111}""", body); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var response = await apiConnection.PostJsonAsync(endpoint, """{"aaaa": 1111}"""); - var result = await response.LoadJsonAsync(); - - Assert.Equal("hogehoge", result); - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task DeleteAsync_Test() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 4175e589d..b72dd5072 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -46,10 +46,6 @@ public interface IApiConnectionLegacy : IApiConnection, IDisposable Task PostAsync(Uri uri, IDictionary? param, IDictionary? media); - Task PostJsonAsync(Uri uri, string json); - - Task> PostJsonAsync(Uri uri, string json); - Task DeleteAsync(Uri uri); } } diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index bfcc99b0d..2b3f651bc 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -351,35 +351,6 @@ await TwitterApiConnection.CheckStatusCode(response) } } - public async Task PostJsonAsync(Uri uri, string json) - { - var request = new PostJsonRequest - { - RequestUri = uri, - JsonString = json, - }; - - using var response = await this.SendAsync(request) - .ConfigureAwait(false); - - return await response.ReadAsString() - .ConfigureAwait(false); - } - - public async Task> PostJsonAsync(Uri uri, string json) - { - var request = new PostJsonRequest - { - RequestUri = uri, - JsonString = json, - }; - - using var response = await this.SendAsync(request) - .ConfigureAwait(false); - - return response.ReadAsLazyJson(); - } - public async Task DeleteAsync(Uri uri) { var requestUri = new Uri(RestApiBase, uri); From 0cf3ada054f61714f7acf56a69e49d215e8c13fa Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 01:50:26 +0900 Subject: [PATCH 53/76] =?UTF-8?q?DeleteRequest=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/DeleteRequestTest.cs | 50 +++++++++++++++++++ OpenTween/Connection/DeleteRequest.cs | 45 +++++++++++++++++ OpenTween/Connection/TwitterApiConnection.cs | 23 +++------ 3 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 OpenTween.Tests/Connection/DeleteRequestTest.cs create mode 100644 OpenTween/Connection/DeleteRequest.cs diff --git a/OpenTween.Tests/Connection/DeleteRequestTest.cs b/OpenTween.Tests/Connection/DeleteRequestTest.cs new file mode 100644 index 000000000..3b72c5b0d --- /dev/null +++ b/OpenTween.Tests/Connection/DeleteRequestTest.cs @@ -0,0 +1,50 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; + +namespace OpenTween.Connection +{ + public class DeleteRequestTest + { + [Fact] + public void CreateMessage_Test() + { + var request = new DeleteRequest + { + RequestUri = new("hoge/aaa.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = "12345", + }, + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Delete, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/hoge/aaa.json?id=12345"), requestMessage.RequestUri); + } + } +} diff --git a/OpenTween/Connection/DeleteRequest.cs b/OpenTween/Connection/DeleteRequest.cs new file mode 100644 index 000000000..f15502814 --- /dev/null +++ b/OpenTween/Connection/DeleteRequest.cs @@ -0,0 +1,45 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class DeleteRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public IDictionary? Query { get; set; } + + public string? EndpointName { get; set; } + + public HttpRequestMessage CreateMessage(Uri baseUri) + => new() + { + Method = HttpMethod.Delete, + RequestUri = GetRequest.BuildUriWithQuery(new(baseUri, this.RequestUri), this.Query), + }; + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 2b3f651bc..93db05aaa 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -353,25 +353,14 @@ await TwitterApiConnection.CheckStatusCode(response) public async Task DeleteAsync(Uri uri) { - var requestUri = new Uri(RestApiBase, uri); - using var request = new HttpRequestMessage(HttpMethod.Delete, requestUri); - - try + var request = new DeleteRequest { - using var response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); + RequestUri = uri, + }; - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } + await this.SendAsync(request) + .IgnoreResponse() + .ConfigureAwait(false); } public static async Task HandleTimeout(Func> func, TimeSpan timeout) From c79d992c15612c7db85c899d1d355e7f22907d41 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 02:05:06 +0900 Subject: [PATCH 54/76] =?UTF-8?q?IApiConnectionLegacy.DeleteAsync=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E7=AE=87?= =?UTF-8?q?=E6=89=80=E3=82=92DeleteRequest=E3=81=AB=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 13 +++++++++---- OpenTween/Api/TwitterApi.cs | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 87df7119d..c660337a3 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -721,10 +721,15 @@ public async Task DirectMessagesEventsDestroy_Test() { var mock = new Mock(); mock.Setup(x => - x.DeleteAsync( - new Uri("direct_messages/events/destroy.json?id=100", UriKind.Relative)) - ) - .Returns(Task.CompletedTask); + x.SendAsync( + It.Is(r => + r.RequestUri == new Uri("direct_messages/events/destroy.json", UriKind.Relative) && + r.Query != null && + r.Query.Count == 1 && + r.Query["id"] == "100" + ) + ) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index e3d3be4f0..342122287 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -468,18 +468,20 @@ public async Task> DirectMessagesEventsNew(l return response.ReadAsLazyJson(); } - public Task DirectMessagesEventsDestroy(TwitterDirectMessageId eventId) + public async Task DirectMessagesEventsDestroy(TwitterDirectMessageId eventId) { - var endpoint = new Uri("direct_messages/events/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new DeleteRequest { - ["id"] = eventId.Id, + RequestUri = new("direct_messages/events/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = eventId.Id, + }, }; - // なぜか application/x-www-form-urlencoded でパラメーターを送ると Bad Request になる謎仕様 - endpoint = new Uri(endpoint.OriginalString + "?" + MyCommon.BuildQueryString(param), UriKind.Relative); - - return this.Connection.DeleteAsync(endpoint); + await this.Connection.SendAsync(request) + .IgnoreResponse() + .ConfigureAwait(false); } public Task UsersShow(string screenName) From 76f1ff02d3592e13e9c9e4ba2bed11c0ab6f2ba0 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 02:06:29 +0900 Subject: [PATCH 55/76] =?UTF-8?q?TwitterApiConnection.DeleteAsync=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 24 ------------------- OpenTween/Connection/IApiConnectionLegacy.cs | 2 -- OpenTween/Connection/TwitterApiConnection.cs | 12 ---------- 3 files changed, 38 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index b793548e3..bfb4b5f96 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -481,30 +481,6 @@ public async Task PostLazyAsync_Multipart_NullTest() Assert.Equal(0, mockHandler.QueueCount); } - [Fact] - public async Task DeleteAsync_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(x => - { - Assert.Equal(HttpMethod.Delete, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - return new HttpResponseMessage(HttpStatusCode.NoContent); - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - - await apiConnection.DeleteAsync(endpoint); - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task HandleTimeout_SuccessTest() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index b72dd5072..7f364f6ac 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -45,7 +45,5 @@ public interface IApiConnectionLegacy : IApiConnection, IDisposable Task> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media); Task PostAsync(Uri uri, IDictionary? param, IDictionary? media); - - Task DeleteAsync(Uri uri); } } diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 93db05aaa..7c7e578f6 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -351,18 +351,6 @@ await TwitterApiConnection.CheckStatusCode(response) } } - public async Task DeleteAsync(Uri uri) - { - var request = new DeleteRequest - { - RequestUri = uri, - }; - - await this.SendAsync(request) - .IgnoreResponse() - .ConfigureAwait(false); - } - public static async Task HandleTimeout(Func> func, TimeSpan timeout) { using var cts = new CancellationTokenSource(); From 1668176ee22fd71149bd25d900af36f9213f0de7 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 01:32:28 +0900 Subject: [PATCH 56/76] =?UTF-8?q?TwitterApiConnection.GetStreamAsync?= =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 46 ------------------- OpenTween/Connection/IApiConnectionLegacy.cs | 4 -- OpenTween/Connection/TwitterApiConnection.cs | 38 --------------- OpenTween/ErrorReportHandler.cs | 12 ----- 4 files changed, 100 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index bfb4b5f96..cc8935625 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -280,52 +280,6 @@ public async Task SendAsync_ErrorJsonTest() Assert.Equal(0, mockHandler.QueueCount); } - [Fact] - public async Task GetStreamAsync_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - using var image = TestUtils.CreateDummyImage(); - apiConnection.Http = http; - - mockHandler.Enqueue(x => - { - Assert.Equal(HttpMethod.Get, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.GetLeftPart(UriPartial.Path)); - - var query = HttpUtility.ParseQueryString(x.RequestUri.Query); - - Assert.Equal("1111", query["aaaa"]); - Assert.Equal("2222", query["bbbb"]); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new ByteArrayContent(image.Stream.ToArray()), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var param = new Dictionary - { - ["aaaa"] = "1111", - ["bbbb"] = "2222", - }; - - var stream = await apiConnection.GetStreamAsync(endpoint, param); - - using (var memoryStream = new MemoryStream()) - { - // 内容の比較のために MemoryStream にコピー - await stream.CopyToAsync(memoryStream); - - Assert.Equal(image.Stream.ToArray(), memoryStream.ToArray()); - } - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task PostLazyAsync_Test() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 7f364f6ac..9b9e7d8c0 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -34,10 +34,6 @@ public interface IApiConnectionLegacy : IApiConnection, IDisposable { Task GetAsync(Uri uri, IDictionary? param, string? endpointName); - Task GetStreamAsync(Uri uri, IDictionary? param); - - Task GetStreamAsync(Uri uri, IDictionary? param, string? endpointName); - Task GetStreamingStreamAsync(Uri uri, IDictionary? param); Task> PostLazyAsync(Uri uri, IDictionary? param); diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 7c7e578f6..bf81c2363 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -164,44 +164,6 @@ private void ThrowIfRateLimitExceeded(string endpointName) } } - public Task GetStreamAsync(Uri uri, IDictionary? param) - => this.GetStreamAsync(uri, param, null); - - public async Task GetStreamAsync(Uri uri, IDictionary? param, string? endpointName) - { - // レートリミット規制中はAPIリクエストを送信せずに直ちにエラーを発生させる - if (endpointName != null) - this.ThrowIfRateLimitExceeded(endpointName); - - var requestUri = new Uri(RestApiBase, uri); - - if (param != null) - requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param)); - - try - { - var response = await this.Http.GetAsync(requestUri) - .ConfigureAwait(false); - - if (endpointName != null) - MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); - - return await response.Content.ReadAsStreamAsync() - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - } - public async Task GetStreamingStreamAsync(Uri uri, IDictionary? param) { var requestUri = new Uri(RestApiBase, uri); diff --git a/OpenTween/ErrorReportHandler.cs b/OpenTween/ErrorReportHandler.cs index af9d572f6..166b1727b 100644 --- a/OpenTween/ErrorReportHandler.cs +++ b/OpenTween/ErrorReportHandler.cs @@ -106,18 +106,6 @@ public static bool IsExceptionIgnorable(Exception ex) return true; } - if (ex is TaskCanceledException cancelEx) - { - // ton.twitter.com の画像でタイムアウトした場合、try-catch で例外がキャッチできない - // https://osdn.net/ticket/browse.php?group_id=6526&tid=37433 - var stackTrace = new StackTrace(cancelEx); - var lastFrameMethod = stackTrace.GetFrame(stackTrace.FrameCount - 1).GetMethod(); - var matchClass = lastFrameMethod.ReflectedType == typeof(TwitterApiConnection); - var matchMethod = lastFrameMethod.Name == nameof(TwitterApiConnection.GetStreamAsync); - if (matchClass && matchMethod) - return true; - } - return false; } } From a5614fe46cf642c88084098da4e5364c7511db7a Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 01:39:40 +0900 Subject: [PATCH 57/76] =?UTF-8?q?TwitterApiConnection.GetStreamingStreamAs?= =?UTF-8?q?ync=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Connection/IApiConnectionLegacy.cs | 2 - OpenTween/Connection/TwitterApiConnection.cs | 48 ++------------------ 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 9b9e7d8c0..839ad462e 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -34,8 +34,6 @@ public interface IApiConnectionLegacy : IApiConnection, IDisposable { Task GetAsync(Uri uri, IDictionary? param, string? endpointName); - Task GetStreamingStreamAsync(Uri uri, IDictionary? param); - Task> PostLazyAsync(Uri uri, IDictionary? param); Task> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media); diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index bf81c2363..0e860607c 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -54,7 +54,6 @@ public static string RestApiHost internal HttpClient Http; internal HttpClient HttpUpload; - internal HttpClient HttpStreaming; internal ITwitterCredential Credential { get; } @@ -71,16 +70,13 @@ public TwitterApiConnection(ITwitterCredential credential) Networking.WebProxyChanged += this.Networking_WebProxyChanged; } - [MemberNotNull(nameof(Http), nameof(HttpUpload), nameof(HttpStreaming))] + [MemberNotNull(nameof(Http), nameof(HttpUpload))] private void InitializeHttpClients() { this.Http = InitializeHttpClient(this.Credential); this.HttpUpload = InitializeHttpClient(this.Credential); this.HttpUpload.Timeout = Networking.UploadImageTimeout; - - this.HttpStreaming = InitializeHttpClient(this.Credential, disableGzip: true); - this.HttpStreaming.Timeout = Timeout.InfiniteTimeSpan; } public async Task SendAsync(IHttpRequest request) @@ -164,35 +160,6 @@ private void ThrowIfRateLimitExceeded(string endpointName) } } - public async Task GetStreamingStreamAsync(Uri uri, IDictionary? param) - { - var requestUri = new Uri(RestApiBase, uri); - - if (param != null) - requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param)); - - try - { - var request = new HttpRequestMessage(HttpMethod.Get, requestUri); - var response = await this.HttpStreaming.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); - - return await response.Content.ReadAsStreamAsync() - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - } - public async Task> PostLazyAsync(Uri uri, IDictionary? param) { var requestUri = new Uri(RestApiBase, uri); @@ -438,7 +405,6 @@ protected virtual void Dispose(bool disposing) Networking.WebProxyChanged -= this.Networking_WebProxyChanged; this.Http.Dispose(); this.HttpUpload.Dispose(); - this.HttpStreaming.Dispose(); } } @@ -524,17 +490,13 @@ await TwitterApiConnection.CheckStatusCode(response) } } - private static HttpClient InitializeHttpClient(ITwitterCredential credential, bool disableGzip = false) + private static HttpClient InitializeHttpClient(ITwitterCredential credential) { var builder = Networking.CreateHttpClientBuilder(); - builder.SetupHttpClientHandler(x => - { - x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); - - if (disableGzip) - x.AutomaticDecompression = DecompressionMethods.None; - }); + builder.SetupHttpClientHandler( + x => x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache) + ); builder.AddHandler(x => credential.CreateHttpHandler(x)); From 67e7ed40d4de62498bf6497a63db4f5680a04e4e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 02:49:58 +0900 Subject: [PATCH 58/76] =?UTF-8?q?GetRequest.BuildUriWithQuery=E3=82=92UriQ?= =?UTF-8?q?ueryBuilder=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Connection/GetRequestTest.cs | 31 ---------- .../Connection/UriQueryBuilderTest.cs | 61 +++++++++++++++++++ OpenTween/Connection/DeleteRequest.cs | 2 +- OpenTween/Connection/GetRequest.cs | 13 +--- OpenTween/Connection/UriQueryBuilder.cs | 42 +++++++++++++ 5 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 OpenTween.Tests/Connection/UriQueryBuilderTest.cs create mode 100644 OpenTween/Connection/UriQueryBuilder.cs diff --git a/OpenTween.Tests/Connection/GetRequestTest.cs b/OpenTween.Tests/Connection/GetRequestTest.cs index db738b66f..efad64638 100644 --- a/OpenTween.Tests/Connection/GetRequestTest.cs +++ b/OpenTween.Tests/Connection/GetRequestTest.cs @@ -46,36 +46,5 @@ public void CreateMessage_Test() Assert.Equal(HttpMethod.Get, requestMessage.Method); Assert.Equal(new("https://api.twitter.com/v1/statuses/show.json?id=12345"), requestMessage.RequestUri); } - - [Fact] - public void BuildUriWithQuery_Test() - { - var uri = new Uri("https://example.com/hoge"); - var query = new Dictionary - { - ["foo"] = "bar", - }; - Assert.Equal(new("https://example.com/hoge?foo=bar"), GetRequest.BuildUriWithQuery(uri, query)); - } - - [Fact] - public void BuildUriWithQuery_NullTest() - { - var uri = new Uri("https://example.com/hoge"); - Assert.Equal(new("https://example.com/hoge"), GetRequest.BuildUriWithQuery(uri, null)); - } - - [Fact] - public void BuildUriWithQuery_CannotMergeTest() - { - var uri = new Uri("https://example.com/hoge?aaa=111"); - var query = new Dictionary - { - ["bbb"] = "222", - }; - Assert.Throws( - () => GetRequest.BuildUriWithQuery(uri, query) - ); - } } } diff --git a/OpenTween.Tests/Connection/UriQueryBuilderTest.cs b/OpenTween.Tests/Connection/UriQueryBuilderTest.cs new file mode 100644 index 000000000..02eaca978 --- /dev/null +++ b/OpenTween.Tests/Connection/UriQueryBuilderTest.cs @@ -0,0 +1,61 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using Xunit; + +namespace OpenTween.Connection +{ + public class UriQueryBuilderTest + { + [Fact] + public void Build_Test() + { + var uri = new Uri("https://example.com/hoge"); + var query = new Dictionary + { + ["foo"] = "bar", + }; + Assert.Equal(new("https://example.com/hoge?foo=bar"), UriQueryBuilder.Build(uri, query)); + } + + [Fact] + public void Build_NullTest() + { + var uri = new Uri("https://example.com/hoge"); + Assert.Equal(new("https://example.com/hoge"), UriQueryBuilder.Build(uri, null)); + } + + [Fact] + public void Build_CannotMergeTest() + { + var uri = new Uri("https://example.com/hoge?aaa=111"); + var query = new Dictionary + { + ["bbb"] = "222", + }; + Assert.Throws( + () => UriQueryBuilder.Build(uri, query) + ); + } + } +} diff --git a/OpenTween/Connection/DeleteRequest.cs b/OpenTween/Connection/DeleteRequest.cs index f15502814..e8bd35361 100644 --- a/OpenTween/Connection/DeleteRequest.cs +++ b/OpenTween/Connection/DeleteRequest.cs @@ -39,7 +39,7 @@ public HttpRequestMessage CreateMessage(Uri baseUri) => new() { Method = HttpMethod.Delete, - RequestUri = GetRequest.BuildUriWithQuery(new(baseUri, this.RequestUri), this.Query), + RequestUri = UriQueryBuilder.Build(new(baseUri, this.RequestUri), this.Query), }; } } diff --git a/OpenTween/Connection/GetRequest.cs b/OpenTween/Connection/GetRequest.cs index 018354a13..48682c3ee 100644 --- a/OpenTween/Connection/GetRequest.cs +++ b/OpenTween/Connection/GetRequest.cs @@ -39,18 +39,7 @@ public HttpRequestMessage CreateMessage(Uri baseUri) => new() { Method = HttpMethod.Get, - RequestUri = BuildUriWithQuery(new(baseUri, this.RequestUri), this.Query), + RequestUri = UriQueryBuilder.Build(new(baseUri, this.RequestUri), this.Query), }; - - public static Uri BuildUriWithQuery(Uri uri, IEnumerable>? query) - { - if (query == null) - return uri; - - if (!MyCommon.IsNullOrEmpty(uri.Query)) - throw new NotSupportedException("Merging uri query is not supported"); - - return new Uri(uri, "?" + MyCommon.BuildQueryString(query)); - } } } diff --git a/OpenTween/Connection/UriQueryBuilder.cs b/OpenTween/Connection/UriQueryBuilder.cs new file mode 100644 index 000000000..2c30dd3ca --- /dev/null +++ b/OpenTween/Connection/UriQueryBuilder.cs @@ -0,0 +1,42 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace OpenTween.Connection +{ + public static class UriQueryBuilder + { + public static Uri Build(Uri uri, IEnumerable>? query) + { + if (query == null) + return uri; + + if (!MyCommon.IsNullOrEmpty(uri.Query)) + throw new NotSupportedException("Merging uri query is not supported"); + + return new(uri, "?" + MyCommon.BuildQueryString(query)); + } + } +} From e8af231b9b1a69b677ad94a5662cfbcc8f4ea5c3 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 03:16:05 +0900 Subject: [PATCH 59/76] =?UTF-8?q?IHttpRequest.Timeout=E3=81=A7=E3=83=AA?= =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88=E6=AF=8E=E3=81=AB=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88=E6=99=82=E9=96=93?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A=E5=8F=AF=E8=83=BD=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Connection/DeleteRequest.cs | 2 ++ OpenTween/Connection/GetRequest.cs | 2 ++ OpenTween/Connection/IHttpRequest.cs | 2 ++ OpenTween/Connection/PostJsonRequest.cs | 2 ++ OpenTween/Connection/TwitterApiConnection.cs | 5 ++++- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/OpenTween/Connection/DeleteRequest.cs b/OpenTween/Connection/DeleteRequest.cs index e8bd35361..60927f2e8 100644 --- a/OpenTween/Connection/DeleteRequest.cs +++ b/OpenTween/Connection/DeleteRequest.cs @@ -35,6 +35,8 @@ public class DeleteRequest : IHttpRequest public string? EndpointName { get; set; } + public TimeSpan Timeout { get; set; } = Networking.DefaultTimeout; + public HttpRequestMessage CreateMessage(Uri baseUri) => new() { diff --git a/OpenTween/Connection/GetRequest.cs b/OpenTween/Connection/GetRequest.cs index 48682c3ee..2a9791d03 100644 --- a/OpenTween/Connection/GetRequest.cs +++ b/OpenTween/Connection/GetRequest.cs @@ -35,6 +35,8 @@ public class GetRequest : IHttpRequest public string? EndpointName { get; set; } + public TimeSpan Timeout { get; set; } = Networking.DefaultTimeout; + public HttpRequestMessage CreateMessage(Uri baseUri) => new() { diff --git a/OpenTween/Connection/IHttpRequest.cs b/OpenTween/Connection/IHttpRequest.cs index 55dd9e3b4..ab309cb4c 100644 --- a/OpenTween/Connection/IHttpRequest.cs +++ b/OpenTween/Connection/IHttpRequest.cs @@ -30,6 +30,8 @@ public interface IHttpRequest { string? EndpointName { get; } + TimeSpan Timeout { get; } + HttpRequestMessage CreateMessage(Uri baseUri); } } diff --git a/OpenTween/Connection/PostJsonRequest.cs b/OpenTween/Connection/PostJsonRequest.cs index 715d54182..236bfe6d6 100644 --- a/OpenTween/Connection/PostJsonRequest.cs +++ b/OpenTween/Connection/PostJsonRequest.cs @@ -35,6 +35,8 @@ public class PostJsonRequest : IHttpRequest public string? EndpointName { get; set; } + public TimeSpan Timeout { get; set; } = Networking.DefaultTimeout; + public HttpRequestMessage CreateMessage(Uri baseUri) => new() { diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 0e860607c..db00455d4 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -75,6 +75,9 @@ private void InitializeHttpClients() { this.Http = InitializeHttpClient(this.Credential); + // タイムアウト設定は IHttpRequest.Timeout でリクエスト毎に制御する + this.Http.Timeout = Timeout.InfiniteTimeSpan; + this.HttpUpload = InitializeHttpClient(this.Credential); this.HttpUpload.Timeout = Networking.UploadImageTimeout; } @@ -94,7 +97,7 @@ public async Task SendAsync(IHttpRequest request) { responseMessage = await HandleTimeout( (token) => this.Http.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, token), - Networking.DefaultTimeout + request.Timeout ); if (endpointName != null) From 7197744db9b0f79c8c18f3c2314c17cd0ac3116e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 02:39:48 +0900 Subject: [PATCH 60/76] =?UTF-8?q?PostRequest=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Connection/PostRequestTest.cs | 70 +++++++++++++++++++ OpenTween/Connection/PostRequest.cs | 48 +++++++++++++ OpenTween/Connection/TwitterApiConnection.cs | 36 ++-------- 3 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 OpenTween.Tests/Connection/PostRequestTest.cs create mode 100644 OpenTween/Connection/PostRequest.cs diff --git a/OpenTween.Tests/Connection/PostRequestTest.cs b/OpenTween.Tests/Connection/PostRequestTest.cs new file mode 100644 index 000000000..fc23f3a63 --- /dev/null +++ b/OpenTween.Tests/Connection/PostRequestTest.cs @@ -0,0 +1,70 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Connection +{ + public class PostRequestTest + { + [Fact] + public void CreateMessage_Test() + { + var request = new PostRequest + { + RequestUri = new("hoge/aaa.json", UriKind.Relative), + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Post, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/hoge/aaa.json"), requestMessage.RequestUri); + Assert.Null(requestMessage.Content); + } + + [Fact] + public async Task CreateMessage_WithQueryTest() + { + var request = new PostRequest + { + RequestUri = new("hoge/aaa.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = "12345", + }, + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Post, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/hoge/aaa.json"), requestMessage.RequestUri); + + using var requestContent = Assert.IsType(requestMessage.Content); + Assert.Equal("id=12345", await requestContent.ReadAsStringAsync()); + } + } +} diff --git a/OpenTween/Connection/PostRequest.cs b/OpenTween/Connection/PostRequest.cs new file mode 100644 index 000000000..122a90dfc --- /dev/null +++ b/OpenTween/Connection/PostRequest.cs @@ -0,0 +1,48 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class PostRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public IDictionary? Query { get; set; } + + public string? EndpointName { get; set; } + + public TimeSpan Timeout { get; set; } = Networking.DefaultTimeout; + + public HttpRequestMessage CreateMessage(Uri baseUri) + => new() + { + Method = HttpMethod.Post, + RequestUri = new(baseUri, this.RequestUri), + Content = this.Query != null ? new FormUrlEncodedContent(this.Query) : null, + }; + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index db00455d4..d17da21fb 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -165,38 +165,16 @@ private void ThrowIfRateLimitExceeded(string endpointName) public async Task> PostLazyAsync(Uri uri, IDictionary? param) { - var requestUri = new Uri(RestApiBase, uri); - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new FormUrlEncodedContent(param); - request.Content = postContent; - - HttpResponseMessage? response = null; - try + var request = new PostRequest { - response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + RequestUri = uri, + Query = param, + }; - var result = new LazyJson(response); - response = null; + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return result; - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - finally - { - response?.Dispose(); - } + return response.ReadAsLazyJson(); } public async Task> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media) From b0dd9456341eaf59f0fd9945f9a5efc30a35bb74 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 03:35:52 +0900 Subject: [PATCH 61/76] =?UTF-8?q?PostMultipartRequest=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/PostMultipartRequestTest.cs | 90 ++++++++++++++++++ .../Connection/TwitterApiConnectionTest.cs | 4 +- OpenTween/Connection/PostMultipartRequest.cs | 66 +++++++++++++ OpenTween/Connection/TwitterApiConnection.cs | 94 ++++--------------- 4 files changed, 175 insertions(+), 79 deletions(-) create mode 100644 OpenTween.Tests/Connection/PostMultipartRequestTest.cs create mode 100644 OpenTween/Connection/PostMultipartRequest.cs diff --git a/OpenTween.Tests/Connection/PostMultipartRequestTest.cs b/OpenTween.Tests/Connection/PostMultipartRequestTest.cs new file mode 100644 index 000000000..043770d86 --- /dev/null +++ b/OpenTween.Tests/Connection/PostMultipartRequestTest.cs @@ -0,0 +1,90 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Connection +{ + public class PostMultipartRequestTest + { + [Fact] + public async Task CreateMessage_Test() + { + using var image = TestUtils.CreateDummyImage(); + using var media = new MemoryImageMediaItem(image); + + var request = new PostMultipartRequest + { + RequestUri = new("hoge/aaa.json", UriKind.Relative), + Query = new Dictionary + { + ["aaaa"] = "1111", + ["bbbb"] = "2222", + }, + Media = new Dictionary + { + ["media1"] = media, + }, + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Post, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/hoge/aaa.json"), requestMessage.RequestUri); + + using var requestContent = Assert.IsType(requestMessage.Content); + var boundary = requestContent.Headers.ContentType.Parameters.Cast() + .First(y => y.Name == "boundary").Value; + + // 前後のダブルクオーテーションを除去 + boundary = boundary.Substring(1, boundary.Length - 2); + + var expectedText = + $"--{boundary}\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "Content-Disposition: form-data; name=aaaa\r\n" + + "\r\n" + + "1111\r\n" + + $"--{boundary}\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "Content-Disposition: form-data; name=bbbb\r\n" + + "\r\n" + + "2222\r\n" + + $"--{boundary}\r\n" + + $"Content-Disposition: form-data; name=media1; filename={media.Name}; filename*=utf-8''{media.Name}\r\n" + + "\r\n"; + + var expected = Encoding.UTF8.GetBytes(expectedText) + .Concat(image.Stream.ToArray()) + .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n")); + + Assert.Equal(expected, await requestContent.ReadAsByteArrayAsync()); + } + } +} diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index cc8935625..37082ca97 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -326,7 +326,7 @@ public async Task PostLazyAsync_MultipartTest() using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); using var apiConnection = new TwitterApiConnection(); - apiConnection.HttpUpload = http; + apiConnection.Http = http; using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); @@ -396,7 +396,7 @@ public async Task PostLazyAsync_Multipart_NullTest() using var mockHandler = new HttpMessageHandlerMock(); using var http = new HttpClient(mockHandler); using var apiConnection = new TwitterApiConnection(); - apiConnection.HttpUpload = http; + apiConnection.Http = http; mockHandler.Enqueue(async x => { diff --git a/OpenTween/Connection/PostMultipartRequest.cs b/OpenTween/Connection/PostMultipartRequest.cs new file mode 100644 index 000000000..6e27e36d2 --- /dev/null +++ b/OpenTween/Connection/PostMultipartRequest.cs @@ -0,0 +1,66 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace OpenTween.Connection +{ + public class PostMultipartRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public IDictionary? Query { get; set; } + + public IDictionary? Media { get; set; } + + public string? EndpointName { get; set; } + + public TimeSpan Timeout { get; set; } = Networking.UploadImageTimeout; + + public HttpRequestMessage CreateMessage(Uri baseUri) + { + var content = new MultipartFormDataContent(); + + if (this.Query != null) + { + foreach (var (key, value) in this.Query) + content.Add(new StringContent(value), key); + } + + if (this.Media != null) + { + foreach (var (key, value) in this.Media) + content.Add(new StreamContent(value.OpenRead()), key, value.Name); + } + + return new() + { + Method = HttpMethod.Post, + RequestUri = new(baseUri, this.RequestUri), + Content = content, + }; + } + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index d17da21fb..3bd735d20 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -53,7 +53,6 @@ public static string RestApiHost public bool IsDisposed { get; private set; } = false; internal HttpClient Http; - internal HttpClient HttpUpload; internal ITwitterCredential Credential { get; } @@ -70,16 +69,13 @@ public TwitterApiConnection(ITwitterCredential credential) Networking.WebProxyChanged += this.Networking_WebProxyChanged; } - [MemberNotNull(nameof(Http), nameof(HttpUpload))] + [MemberNotNull(nameof(Http))] private void InitializeHttpClients() { this.Http = InitializeHttpClient(this.Credential); // タイムアウト設定は IHttpRequest.Timeout でリクエスト毎に制御する this.Http.Timeout = Timeout.InfiniteTimeSpan; - - this.HttpUpload = InitializeHttpClient(this.Credential); - this.HttpUpload.Timeout = Networking.UploadImageTimeout; } public async Task SendAsync(IHttpRequest request) @@ -179,86 +175,31 @@ public async Task> PostLazyAsync(Uri uri, IDictionary> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media) { - var requestUri = new Uri(RestApiBase, uri); - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new MultipartFormDataContent(); - if (param != null) - { - foreach (var (key, value) in param) - postContent.Add(new StringContent(value), key); - } - if (media != null) - { - foreach (var (key, value) in media) - postContent.Add(new StreamContent(value.OpenRead()), key, value.Name); - } - - request.Content = postContent; - - HttpResponseMessage? response = null; - try + var request = new PostMultipartRequest { - response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + RequestUri = uri, + Query = param, + Media = media, + }; - var result = new LazyJson(response); - response = null; + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return result; - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - finally - { - response?.Dispose(); - } + return response.ReadAsLazyJson(); } public async Task PostAsync(Uri uri, IDictionary? param, IDictionary? media) { - var requestUri = new Uri(RestApiBase, uri); - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new MultipartFormDataContent(); - if (param != null) - { - foreach (var (key, value) in param) - postContent.Add(new StringContent(value), key); - } - if (media != null) - { - foreach (var (key, value) in media) - postContent.Add(new StreamContent(value.OpenRead()), key, value.Name); - } - - request.Content = postContent; - - try + var request = new PostMultipartRequest { - using var response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); + RequestUri = uri, + Query = param, + Media = media, + }; - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } + await this.SendAsync(request) + .IgnoreResponse() + .ConfigureAwait(false); } public static async Task HandleTimeout(Func> func, TimeSpan timeout) @@ -385,7 +326,6 @@ protected virtual void Dispose(bool disposing) { Networking.WebProxyChanged -= this.Networking_WebProxyChanged; this.Http.Dispose(); - this.HttpUpload.Dispose(); } } From 82ab8b0fb10d4515d616f25c7026bf9ab4f0e1df Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 03:56:05 +0900 Subject: [PATCH 62/76] =?UTF-8?q?IMediaItem=E3=82=92=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=8F=96=E3=82=8BIApiConnectionLegacy.PostLazyAsync?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E7=AE=87=E6=89=80=E3=82=92PostMultipartRequest=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 68 +++++++++++++++++++-------- OpenTween/Api/TwitterApi.cs | 55 +++++++++++++--------- 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index c660337a3..c6b24ab29 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -1143,21 +1143,35 @@ await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", [Fact] public async Task AccountUpdateProfileImage_Test() { + using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); + + Func verifyRequest = r => + { + Assert.Equal(new("account/update_profile_image.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + var expectedMedia = new Dictionary + { + ["image"] = media, + }; + Assert.Equal(expectedMedia, r.Media); + return true; + }; + var mock = new Mock(); mock.Setup(x => - x.PostLazyAsync( - new Uri("account/update_profile_image.json", UriKind.Relative), - new Dictionary - { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - new Dictionary { { "image", media } }) + x.SendAsync( + It.Is(r => verifyRequest(r)) + ) ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + .ReturnsAsync(new ApiResponse(responseMessage)); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1237,21 +1251,35 @@ await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", me [Fact] public async Task MediaUploadAppend_Test() { + using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); + + Func verifyRequest = r => + { + Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri); + var expectedQuery = new Dictionary + { + ["command"] = "APPEND", + ["media_id"] = "11111", + ["segment_index"] = "1", + }; + Assert.Equal(expectedQuery, r.Query); + var expectedMedia = new Dictionary + { + ["media"] = media, + }; + Assert.Equal(expectedMedia, r.Media); + return true; + }; + var mock = new Mock(); mock.Setup(x => - x.PostAsync( - new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), - new Dictionary - { - { "command", "APPEND" }, - { "media_id", "11111" }, - { "segment_index", "1" }, - }, - new Dictionary { { "media", media } }) + x.SendAsync( + It.Is(r => verifyRequest(r)) + ) ) - .Returns(Task.CompletedTask); + .ReturnsAsync(new ApiResponse(responseMessage)); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 342122287..5e3c55825 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -713,21 +713,27 @@ public Task> AccountUpdateProfile(string name, string url, return this.Connection.PostLazyAsync(endpoint, param); } - public Task> AccountUpdateProfileImage(IMediaItem image) + public async Task> AccountUpdateProfileImage(IMediaItem image) { - var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative); - var param = new Dictionary + var request = new PostMultipartRequest { - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", - }; - var paramMedia = new Dictionary - { - ["image"] = image, + RequestUri = new("account/update_profile_image.json", UriKind.Relative), + Query = new Dictionary + { + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + Media = new Dictionary + { + ["image"] = image, + }, }; - return this.Connection.PostLazyAsync(endpoint, param, paramMedia); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task ApplicationRateLimitStatus() @@ -760,21 +766,26 @@ public Task> MediaUploadInit(long totalBytes, s return this.Connection.PostLazyAsync(endpoint, param); } - public Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media) + public async Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media) { - var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json"); - var param = new Dictionary + var request = new PostMultipartRequest { - ["command"] = "APPEND", - ["media_id"] = mediaId.ToString(), - ["segment_index"] = segmentIndex.ToString(), - }; - var paramMedia = new Dictionary - { - ["media"] = media, + RequestUri = new("https://upload.twitter.com/1.1/media/upload.json"), + Query = new Dictionary + { + ["command"] = "APPEND", + ["media_id"] = mediaId.ToString(), + ["segment_index"] = segmentIndex.ToString(), + }, + Media = new Dictionary + { + ["media"] = media, + }, }; - return this.Connection.PostAsync(endpoint, param, paramMedia); + await this.Connection.SendAsync(request) + .IgnoreResponse() + .ConfigureAwait(false); } public Task> MediaUploadFinalize(long mediaId) From de304b83c79303d6627429ecb6092d71cce57480 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Tue, 12 Dec 2023 03:57:47 +0900 Subject: [PATCH 63/76] =?UTF-8?q?IMediaItem=E3=82=92=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=8F=96=E3=82=8BTwitterApiConnection.PostLazyAsync?= =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 115 ------------------ OpenTween/Connection/IApiConnectionLegacy.cs | 4 - OpenTween/Connection/TwitterApiConnection.cs | 29 ----- 3 files changed, 148 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index 37082ca97..b2c206cde 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -320,121 +320,6 @@ public async Task PostLazyAsync_Test() Assert.Equal(0, mockHandler.QueueCount); } - [Fact] - public async Task PostLazyAsync_MultipartTest() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - using var image = TestUtils.CreateDummyImage(); - using var media = new MemoryImageMediaItem(image); - - mockHandler.Enqueue(async x => - { - Assert.Equal(HttpMethod.Post, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - Assert.IsType(x.Content); - - var boundary = x.Content.Headers.ContentType.Parameters.Cast() - .First(y => y.Name == "boundary").Value; - - // 前後のダブルクオーテーションを除去 - boundary = boundary.Substring(1, boundary.Length - 2); - - var expectedText = - $"--{boundary}\r\n" + - "Content-Type: text/plain; charset=utf-8\r\n" + - "Content-Disposition: form-data; name=aaaa\r\n" + - "\r\n" + - "1111\r\n" + - $"--{boundary}\r\n" + - "Content-Type: text/plain; charset=utf-8\r\n" + - "Content-Disposition: form-data; name=bbbb\r\n" + - "\r\n" + - "2222\r\n" + - $"--{boundary}\r\n" + - $"Content-Disposition: form-data; name=media1; filename={media.Name}; filename*=utf-8''{media.Name}\r\n" + - "\r\n"; - - var expected = Encoding.UTF8.GetBytes(expectedText) - .Concat(image.Stream.ToArray()) - .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n")); - - Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync()); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var param = new Dictionary - { - ["aaaa"] = "1111", - ["bbbb"] = "2222", - }; - var mediaParam = new Dictionary - { - ["media1"] = media, - }; - - var result = await apiConnection.PostLazyAsync(endpoint, param, mediaParam); - - Assert.Equal("hogehoge", await result.LoadJsonAsync()); - - Assert.Equal(0, mockHandler.QueueCount); - } - - [Fact] - public async Task PostLazyAsync_Multipart_NullTest() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(async x => - { - Assert.Equal(HttpMethod.Post, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - Assert.IsType(x.Content); - - var boundary = x.Content.Headers.ContentType.Parameters.Cast() - .First(y => y.Name == "boundary").Value; - - // 前後のダブルクオーテーションを除去 - boundary = boundary.Substring(1, boundary.Length - 2); - - var expectedText = - $"--{boundary}\r\n" + - $"\r\n--{boundary}--\r\n"; - - var expected = Encoding.UTF8.GetBytes(expectedText); - - Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync()); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - - var result = await apiConnection.PostLazyAsync(endpoint, param: null, media: null); - - Assert.Equal("hogehoge", await result.LoadJsonAsync()); - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task HandleTimeout_SuccessTest() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 839ad462e..800300eda 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -35,9 +35,5 @@ public interface IApiConnectionLegacy : IApiConnection, IDisposable Task GetAsync(Uri uri, IDictionary? param, string? endpointName); Task> PostLazyAsync(Uri uri, IDictionary? param); - - Task> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media); - - Task PostAsync(Uri uri, IDictionary? param, IDictionary? media); } } diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 3bd735d20..02c7458e3 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -173,35 +173,6 @@ public async Task> PostLazyAsync(Uri uri, IDictionary(); } - public async Task> PostLazyAsync(Uri uri, IDictionary? param, IDictionary? media) - { - var request = new PostMultipartRequest - { - RequestUri = uri, - Query = param, - Media = media, - }; - - using var response = await this.SendAsync(request) - .ConfigureAwait(false); - - return response.ReadAsLazyJson(); - } - - public async Task PostAsync(Uri uri, IDictionary? param, IDictionary? media) - { - var request = new PostMultipartRequest - { - RequestUri = uri, - Query = param, - Media = media, - }; - - await this.SendAsync(request) - .IgnoreResponse() - .ConfigureAwait(false); - } - public static async Task HandleTimeout(Func> func, TimeSpan timeout) { using var cts = new CancellationTokenSource(); From 0bfed7e74d372752fb0e819ccb3ad56ed74c5240 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 00:33:41 +0900 Subject: [PATCH 64/76] =?UTF-8?q?TwitterApiTest=E3=81=A7=E3=83=AA=E3=82=AF?= =?UTF-8?q?=E3=82=A8=E3=82=B9=E3=83=88=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=A2=E3=83=83=E3=82=AF=E3=82=92=E7=94=9F=E6=88=90?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81=E3=81=AE=E3=83=98=E3=83=AB?= =?UTF-8?q?=E3=83=91=E3=83=BC=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 101 ++++++++++++-------------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index c6b24ab29..559e046e6 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -79,6 +79,28 @@ public void Initialize_Test() Assert.Equal("foobar", twitterApi.CurrentScreenName); } + private Mock CreateApiConnectionMock(Action verifyRequest) + where T : IHttpRequest + { + Func verifyRequestWrapper = r => + { + verifyRequest(r); + // Assert メソッドを使用する想定のため、失敗した場合は例外が発生しここまで到達しない + return true; + }; + + var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); + var mock = new Mock(); + mock.Setup(x => + x.SendAsync( + It.Is(r => verifyRequestWrapper(r)) + ) + ) + .ReturnsAsync(new ApiResponse(responseMessage)); + + return mock; + } + [Fact] public async Task StatusesHomeTimeline_Test() { @@ -675,8 +697,6 @@ public async Task DirectMessagesEventsList_Test() [Fact] public async Task DirectMessagesEventsNew_Test() { - using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); - var mock = new Mock(); var requestJson = """ { "event": { @@ -698,15 +718,12 @@ public async Task DirectMessagesEventsNew_Test() } } """; - mock.Setup(x => - x.SendAsync( - It.Is(r => - r.RequestUri == new Uri("direct_messages/events/new.json", UriKind.Relative) && - r.JsonString == requestJson - ) - ) - ) - .ReturnsAsync(new ApiResponse(responseMessage)); + + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("direct_messages/events/new.json", UriKind.Relative), r.RequestUri); + Assert.Equal(requestJson, r.JsonString); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -719,17 +736,15 @@ public async Task DirectMessagesEventsNew_Test() [Fact] public async Task DirectMessagesEventsDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.SendAsync( - It.Is(r => - r.RequestUri == new Uri("direct_messages/events/destroy.json", UriKind.Relative) && - r.Query != null && - r.Query.Count == 1 && - r.Query["id"] == "100" - ) - ) - ); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("direct_messages/events/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1143,11 +1158,10 @@ await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", [Fact] public async Task AccountUpdateProfileImage_Test() { - using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); - Func verifyRequest = r => + var mock = this.CreateApiConnectionMock(r => { Assert.Equal(new("account/update_profile_image.json", UriKind.Relative), r.RequestUri); var expectedQuery = new Dictionary @@ -1162,16 +1176,7 @@ public async Task AccountUpdateProfileImage_Test() ["image"] = media, }; Assert.Equal(expectedMedia, r.Media); - return true; - }; - - var mock = new Mock(); - mock.Setup(x => - x.SendAsync( - It.Is(r => verifyRequest(r)) - ) - ) - .ReturnsAsync(new ApiResponse(responseMessage)); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1251,11 +1256,10 @@ await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", me [Fact] public async Task MediaUploadAppend_Test() { - using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); using var image = TestUtils.CreateDummyImage(); using var media = new MemoryImageMediaItem(image); - Func verifyRequest = r => + var mock = this.CreateApiConnectionMock(r => { Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri); var expectedQuery = new Dictionary @@ -1270,16 +1274,7 @@ public async Task MediaUploadAppend_Test() ["media"] = media, }; Assert.Equal(expectedMedia, r.Media); - return true; - }; - - var mock = new Mock(); - mock.Setup(x => - x.SendAsync( - It.Is(r => verifyRequest(r)) - ) - ) - .ReturnsAsync(new ApiResponse(responseMessage)); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1340,15 +1335,11 @@ public async Task MediaUploadStatus_Test() [Fact] public async Task MediaMetadataCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.SendAsync( - It.Is(r => - r.RequestUri == new Uri("https://upload.twitter.com/1.1/media/metadata/create.json") && - r.JsonString == """{"media_id": "12345", "alt_text": {"text": "hogehoge"}}""" - ) - ) - ); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("https://upload.twitter.com/1.1/media/metadata/create.json"), r.RequestUri); + Assert.Equal("""{"media_id": "12345", "alt_text": {"text": "hogehoge"}}""", r.JsonString); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; From 08f5cba818a1f0184b42920bcd64f2479b1954bc Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 00:51:55 +0900 Subject: [PATCH 65/76] =?UTF-8?q?IApiConnectionLegacy.PostLazyAsync?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E7=AE=87=E6=89=80=E3=82=92PostRequest=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 438 +++++++++++++------------- OpenTween/Api/TwitterApi.cs | 312 ++++++++++++------ 2 files changed, 429 insertions(+), 321 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 559e046e6..9266c60ff 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -243,24 +243,23 @@ public async Task StatusesLookup_Test() [Fact] public async Task StatusesUpdate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("statuses/update.json", UriKind.Relative), - new Dictionary - { - { "status", "hogehoge" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "in_reply_to_status_id", "100" }, - { "media_ids", "10,20" }, - { "auto_populate_reply_metadata", "true" }, - { "exclude_reply_user_ids", "100,200" }, - { "attachment_url", "https://twitter.com/twitterapi/status/22634515958" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("statuses/update.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["status"] = "hogehoge", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["in_reply_to_status_id"] = "100", + ["media_ids"] = "10,20", + ["auto_populate_reply_metadata"] = "true", + ["exclude_reply_user_ids"] = "100,200", + ["attachment_url"] = "https://twitter.com/twitterapi/status/22634515958", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -281,20 +280,19 @@ await twitterApi.StatusesUpdate( [Fact] public async Task StatusesUpdate_ExcludeReplyUserIdsEmptyTest() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("statuses/update.json", UriKind.Relative), - new Dictionary - { - { "status", "hogehoge" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - // exclude_reply_user_ids は空の場合には送信されない - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("statuses/update.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["status"] = "hogehoge", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + // exclude_reply_user_ids は空の場合には送信されない + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -308,13 +306,15 @@ await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, exc [Fact] public async Task StatusesDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("statuses/destroy.json", UriKind.Relative), - new Dictionary { { "id", "100" } }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("statuses/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -328,19 +328,18 @@ public async Task StatusesDestroy_Test() [Fact] public async Task StatusesRetweet_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("statuses/retweet.json", UriKind.Relative), - new Dictionary - { - { "id", "100" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("statuses/retweet.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["id"] = "100", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -461,18 +460,17 @@ public async Task ListsMemberships_Test() [Fact] public async Task ListsCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("lists/create.json", UriKind.Relative), - new Dictionary - { - { "name", "hogehoge" }, - { "description", "aaaa" }, - { "mode", "private" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterList())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("lists/create.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["name"] = "hogehoge", + ["description"] = "aaaa", + ["mode"] = "private", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -486,19 +484,18 @@ await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true) [Fact] public async Task ListsUpdate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("lists/update.json", UriKind.Relative), - new Dictionary - { - { "list_id", "12345" }, - { "name", "hogehoge" }, - { "description", "aaaa" }, - { "mode", "private" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterList())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("lists/update.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["list_id"] = "12345", + ["name"] = "hogehoge", + ["description"] = "aaaa", + ["mode"] = "private", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -512,16 +509,15 @@ await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @pri [Fact] public async Task ListsDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("lists/destroy.json", UriKind.Relative), - new Dictionary - { - { "list_id", "12345" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterList())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("lists/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["list_id"] = "12345", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -619,20 +615,19 @@ public async Task ListsMembersShow_Test() [Fact] public async Task ListsMembersCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("lists/members/create.json", UriKind.Relative), - new Dictionary - { - { "list_id", "12345" }, - { "screen_name", "twitterapi" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("lists/members/create.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["list_id"] = "12345", + ["screen_name"] = "twitterapi", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -646,20 +641,19 @@ await twitterApi.ListsMembersCreate(12345L, "twitterapi") [Fact] public async Task ListsMembersDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("lists/members/destroy.json", UriKind.Relative), - new Dictionary - { - { "list_id", "12345" }, - { "screen_name", "twitterapi" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("lists/members/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["list_id"] = "12345", + ["screen_name"] = "twitterapi", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -809,17 +803,16 @@ public async Task UsersLookup_Test() [Fact] public async Task UsersReportSpam_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("users/report_spam.json", UriKind.Relative), - new Dictionary - { - { "screen_name", "twitterapi" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser { ScreenName = "twitterapi" })); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("users/report_spam.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["screen_name"] = "twitterapi", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -861,17 +854,16 @@ public async Task FavoritesList_Test() [Fact] public async Task FavoritesCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("favorites/create.json", UriKind.Relative), - new Dictionary - { - { "id", "100" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("favorites/create.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["id"] = "100", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -885,17 +877,16 @@ public async Task FavoritesCreate_Test() [Fact] public async Task FavoritesDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("favorites/destroy.json", UriKind.Relative), - new Dictionary - { - { "id", "100" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L })); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("favorites/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["id"] = "100", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -929,13 +920,15 @@ public async Task FriendshipsShow_Test() [Fact] public async Task FriendshipsCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("friendships/create.json", UriKind.Relative), - new Dictionary { { "screen_name", "twitterapi" } }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterFriendship())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("friendships/create.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["screen_name"] = "twitterapi", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -949,13 +942,15 @@ await twitterApi.FriendshipsCreate(screenName: "twitterapi") [Fact] public async Task FriendshipsDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("friendships/destroy.json", UriKind.Relative), - new Dictionary { { "screen_name", "twitterapi" } }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterFriendship())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("friendships/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["screen_name"] = "twitterapi", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1049,17 +1044,16 @@ public async Task BlocksIds_Test() [Fact] public async Task BlocksCreate_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("blocks/create.json", UriKind.Relative), - new Dictionary - { - { "screen_name", "twitterapi" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("blocks/create.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["screen_name"] = "twitterapi", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1073,17 +1067,16 @@ await twitterApi.BlocksCreate(screenName: "twitterapi") [Fact] public async Task BlocksDestroy_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("blocks/destroy.json", UriKind.Relative), - new Dictionary - { - { "screen_name", "twitterapi" }, - { "tweet_mode", "extended" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("blocks/destroy.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["screen_name"] = "twitterapi", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1129,22 +1122,21 @@ public async Task AccountVerifyCredentials_Test() [Fact] public async Task AccountUpdateProfile_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("account/update_profile.json", UriKind.Relative), - new Dictionary - { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "name", "Name" }, - { "url", "http://example.com/" }, - { "location", "Location" }, - { "description", "<script>alert(1)</script>" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUser())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("account/update_profile.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["name"] = "Name", + ["url"] = "http://example.com/", + ["location"] = "Location", + ["description"] = "<script>alert(1)</script>", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1230,19 +1222,18 @@ public async Task Configuration_Test() [Fact] public async Task MediaUploadInit_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), - new Dictionary - { - { "command", "INIT" }, - { "total_bytes", "123456" }, - { "media_type", "image/png" }, - { "media_category", "dm_image" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaInit())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri); + var expectedQuery = new Dictionary + { + ["command"] = "INIT", + ["total_bytes"] = "123456", + ["media_type"] = "image/png", + ["media_category"] = "dm_image", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1287,17 +1278,16 @@ public async Task MediaUploadAppend_Test() [Fact] public async Task MediaUploadFinalize_Test() { - var mock = new Mock(); - mock.Setup(x => - x.PostLazyAsync( - new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), - new Dictionary - { - { "command", "FINALIZE" }, - { "media_id", "11111" }, - }) - ) - .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaResult())); + var mock = this.CreateApiConnectionMock(r => + { + Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri); + var expectedQuery = new Dictionary + { + ["command"] = "FINALIZE", + ["media_id"] = "11111", + }; + Assert.Equal(expectedQuery, r.Query); + }); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 5e3c55825..d5720327a 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -152,7 +152,7 @@ public Task StatusesLookup(IReadOnlyList statusIds) return this.Connection.GetAsync(endpoint, param, "/statuses/lookup"); } - public Task> StatusesUpdate( + public async Task> StatusesUpdate( string status, TwitterStatusId? replyToId, IReadOnlyList? mediaIds, @@ -160,7 +160,6 @@ public Task> StatusesUpdate( IReadOnlyList? excludeReplyUserIds = null, string? attachmentUrl = null) { - var endpoint = new Uri("statuses/update.json", UriKind.Relative); var param = new Dictionary { ["status"] = status, @@ -180,32 +179,53 @@ public Task> StatusesUpdate( if (attachmentUrl != null) param["attachment_url"] = attachmentUrl; - return this.Connection.PostLazyAsync(endpoint, param); + var request = new PostRequest + { + RequestUri = new("statuses/update.json", UriKind.Relative), + Query = param, + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> StatusesDestroy(TwitterStatusId statusId) + public async Task> StatusesDestroy(TwitterStatusId statusId) { - var endpoint = new Uri("statuses/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["id"] = statusId.Id, + RequestUri = new("statuses/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = statusId.Id, + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> StatusesRetweet(TwitterStatusId statusId) + public async Task> StatusesRetweet(TwitterStatusId statusId) { - var endpoint = new Uri("statuses/retweet.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["id"] = statusId.Id, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("statuses/retweet.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = statusId.Id, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task SearchTweets(string query, string? lang = null, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) @@ -282,9 +302,8 @@ public Task ListsMemberships(string screenName, long? cursor = nul return this.Connection.GetAsync(endpoint, param, "/lists/memberships"); } - public Task> ListsCreate(string name, string? description = null, bool? @private = null) + public async Task> ListsCreate(string name, string? description = null, bool? @private = null) { - var endpoint = new Uri("lists/create.json", UriKind.Relative); var param = new Dictionary { ["name"] = name, @@ -295,12 +314,20 @@ public Task> ListsCreate(string name, string? description if (@private != null) param["mode"] = @private.Value ? "private" : "public"; - return this.Connection.PostLazyAsync(endpoint, param); + var request = new PostRequest + { + RequestUri = new("lists/create.json", UriKind.Relative), + Query = param, + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> ListsUpdate(long listId, string? name = null, string? description = null, bool? @private = null) + public async Task> ListsUpdate(long listId, string? name = null, string? description = null, bool? @private = null) { - var endpoint = new Uri("lists/update.json", UriKind.Relative); var param = new Dictionary { ["list_id"] = listId.ToString(), @@ -313,18 +340,33 @@ public Task> ListsUpdate(long listId, string? name = null, if (@private != null) param["mode"] = @private.Value ? "private" : "public"; - return this.Connection.PostLazyAsync(endpoint, param); + var request = new PostRequest + { + RequestUri = new("lists/update.json", UriKind.Relative), + Query = param, + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> ListsDestroy(long listId) + public async Task> ListsDestroy(long listId) { - var endpoint = new Uri("lists/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["list_id"] = listId.ToString(), + RequestUri = new("lists/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["list_id"] = listId.ToString(), + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task ListsStatuses(long listId, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null, bool? includeRTs = null) @@ -382,34 +424,46 @@ public Task ListsMembersShow(long listId, string screenName) return this.Connection.GetAsync(endpoint, param, "/lists/members/show"); } - public Task> ListsMembersCreate(long listId, string screenName) + public async Task> ListsMembersCreate(long listId, string screenName) { - var endpoint = new Uri("lists/members/create.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["list_id"] = listId.ToString(), - ["screen_name"] = screenName, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("lists/members/create.json", UriKind.Relative), + Query = new Dictionary + { + ["list_id"] = listId.ToString(), + ["screen_name"] = screenName, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> ListsMembersDestroy(long listId, string screenName) + public async Task> ListsMembersDestroy(long listId, string screenName) { - var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["list_id"] = listId.ToString(), - ["screen_name"] = screenName, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("lists/members/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["list_id"] = listId.ToString(), + ["screen_name"] = screenName, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task DirectMessagesEventsList(int? count = null, string? cursor = null) @@ -512,16 +566,22 @@ public Task UsersLookup(IReadOnlyList userIds) return this.Connection.GetAsync(endpoint, param, "/users/lookup"); } - public Task> UsersReportSpam(string screenName) + public async Task> UsersReportSpam(string screenName) { - var endpoint = new Uri("users/report_spam.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["screen_name"] = screenName, - ["tweet_mode"] = "extended", + RequestUri = new("users/report_spam.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task FavoritesList(int? count = null, long? maxId = null, long? sinceId = null) @@ -544,28 +604,40 @@ public Task FavoritesList(int? count = null, long? maxId = null return this.Connection.GetAsync(endpoint, param, "/favorites/list"); } - public Task> FavoritesCreate(TwitterStatusId statusId) + public async Task> FavoritesCreate(TwitterStatusId statusId) { - var endpoint = new Uri("favorites/create.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["id"] = statusId.Id, - ["tweet_mode"] = "extended", + RequestUri = new("favorites/create.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = statusId.Id, + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> FavoritesDestroy(TwitterStatusId statusId) + public async Task> FavoritesDestroy(TwitterStatusId statusId) { - var endpoint = new Uri("favorites/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["id"] = statusId.Id, - ["tweet_mode"] = "extended", + RequestUri = new("favorites/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = statusId.Id, + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task FriendshipsShow(string sourceScreenName, string targetScreenName) @@ -580,26 +652,38 @@ public Task FriendshipsShow(string sourceScreenName, string t return this.Connection.GetAsync(endpoint, param, "/friendships/show"); } - public Task> FriendshipsCreate(string screenName) + public async Task> FriendshipsCreate(string screenName) { - var endpoint = new Uri("friendships/create.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["screen_name"] = screenName, + RequestUri = new("friendships/create.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> FriendshipsDestroy(string screenName) + public async Task> FriendshipsDestroy(string screenName) { - var endpoint = new Uri("friendships/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["screen_name"] = screenName, + RequestUri = new("friendships/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task NoRetweetIds() @@ -642,28 +726,40 @@ public Task BlocksIds(long? cursor = null) return this.Connection.GetAsync(endpoint, param, "/blocks/ids"); } - public Task> BlocksCreate(string screenName) + public async Task> BlocksCreate(string screenName) { - var endpoint = new Uri("blocks/create.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["screen_name"] = screenName, - ["tweet_mode"] = "extended", + RequestUri = new("blocks/create.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } - public Task> BlocksDestroy(string screenName) + public async Task> BlocksDestroy(string screenName) { - var endpoint = new Uri("blocks/destroy.json", UriKind.Relative); - var param = new Dictionary + var request = new PostRequest { - ["screen_name"] = screenName, - ["tweet_mode"] = "extended", + RequestUri = new("blocks/destroy.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + ["tweet_mode"] = "extended", + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public async Task AccountVerifyCredentials() @@ -685,9 +781,8 @@ public async Task AccountVerifyCredentials() return user; } - public Task> AccountUpdateProfile(string name, string url, string? location, string? description) + public async Task> AccountUpdateProfile(string name, string url, string? location, string? description) { - var endpoint = new Uri("account/update_profile.json", UriKind.Relative); var param = new Dictionary { ["include_entities"] = "true", @@ -710,7 +805,16 @@ public Task> AccountUpdateProfile(string name, string url, param["description"] = escapedDescription; } - return this.Connection.PostLazyAsync(endpoint, param); + var request = new PostRequest + { + RequestUri = new("account/update_profile.json", UriKind.Relative), + Query = param, + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public async Task> AccountUpdateProfileImage(IMediaItem image) @@ -750,9 +854,8 @@ public Task Configuration() return this.Connection.GetAsync(endpoint, null, "/help/configuration"); } - public Task> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null) + public async Task> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null) { - var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json"); var param = new Dictionary { ["command"] = "INIT", @@ -763,7 +866,16 @@ public Task> MediaUploadInit(long totalBytes, s if (mediaCategory != null) param["media_category"] = mediaCategory; - return this.Connection.PostLazyAsync(endpoint, param); + var request = new PostRequest + { + RequestUri = new("https://upload.twitter.com/1.1/media/upload.json"), + Query = param, + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public async Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media) @@ -788,16 +900,22 @@ await this.Connection.SendAsync(request) .ConfigureAwait(false); } - public Task> MediaUploadFinalize(long mediaId) + public async Task> MediaUploadFinalize(long mediaId) { - var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json"); - var param = new Dictionary + var request = new PostRequest { - ["command"] = "FINALIZE", - ["media_id"] = mediaId.ToString(), + RequestUri = new("https://upload.twitter.com/1.1/media/upload.json"), + Query = new Dictionary + { + ["command"] = "FINALIZE", + ["media_id"] = mediaId.ToString(), + }, }; - return this.Connection.PostLazyAsync(endpoint, param); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return response.ReadAsLazyJson(); } public Task MediaUploadStatus(long mediaId) From 266aa22808bb9e5de79cc1b6c05050eaa1ccca62 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 00:53:12 +0900 Subject: [PATCH 66/76] =?UTF-8?q?TwitterApiConnection.PostLazyAsync?= =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 40 ------------------- OpenTween/Connection/IApiConnectionLegacy.cs | 2 - OpenTween/Connection/TwitterApiConnection.cs | 14 ------- 3 files changed, 56 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index b2c206cde..6c683cabe 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -280,46 +280,6 @@ public async Task SendAsync_ErrorJsonTest() Assert.Equal(0, mockHandler.QueueCount); } - [Fact] - public async Task PostLazyAsync_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(async x => - { - Assert.Equal(HttpMethod.Post, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.AbsoluteUri); - - var body = await x.Content.ReadAsStringAsync(); - var query = HttpUtility.ParseQueryString(body); - - Assert.Equal("1111", query["aaaa"]); - Assert.Equal("2222", query["bbbb"]); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var param = new Dictionary - { - ["aaaa"] = "1111", - ["bbbb"] = "2222", - }; - - var result = await apiConnection.PostLazyAsync(endpoint, param); - - Assert.Equal("hogehoge", await result.LoadJsonAsync()); - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task HandleTimeout_SuccessTest() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 800300eda..67618ac19 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -33,7 +33,5 @@ namespace OpenTween.Connection public interface IApiConnectionLegacy : IApiConnection, IDisposable { Task GetAsync(Uri uri, IDictionary? param, string? endpointName); - - Task> PostLazyAsync(Uri uri, IDictionary? param); } } diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 02c7458e3..f2b3c28df 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -159,20 +159,6 @@ private void ThrowIfRateLimitExceeded(string endpointName) } } - public async Task> PostLazyAsync(Uri uri, IDictionary? param) - { - var request = new PostRequest - { - RequestUri = uri, - Query = param, - }; - - using var response = await this.SendAsync(request) - .ConfigureAwait(false); - - return response.ReadAsLazyJson(); - } - public static async Task HandleTimeout(Func> func, TimeSpan timeout) { using var cts = new CancellationTokenSource(); From 45f3a4816516cf43359fa8c38b08f0e271001ffc Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 02:38:24 +0900 Subject: [PATCH 67/76] =?UTF-8?q?IApiConnectionLegacy.GetAsync=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E7=AE=87?= =?UTF-8?q?=E6=89=80=E3=82=92GetRequest=E3=81=AB=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 685 ++++++++++-------- .../Api/TwitterV2/GetTimelineRequestTest.cs | 41 +- OpenTween/Api/TwitterApi.cs | 446 +++++++++--- OpenTween/Api/TwitterV2/GetTimelineRequest.cs | 16 +- 4 files changed, 751 insertions(+), 437 deletions(-) diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 9266c60ff..f19ca8499 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -81,6 +81,10 @@ public void Initialize_Test() private Mock CreateApiConnectionMock(Action verifyRequest) where T : IHttpRequest + => this.CreateApiConnectionMock(verifyRequest, ""); + + private Mock CreateApiConnectionMock(Action verifyRequest, string responseText) + where T : IHttpRequest { Func verifyRequestWrapper = r => { @@ -89,7 +93,10 @@ private Mock CreateApiConnectionMock(Action verifyRe return true; }; - var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); + var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(responseText), + }; var mock = new Mock(); mock.Setup(x => x.SendAsync( @@ -104,22 +111,24 @@ private Mock CreateApiConnectionMock(Action verifyRe [Fact] public async Task StatusesHomeTimeline_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("statuses/home_timeline.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("statuses/home_timeline.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - }, - "/statuses/home_timeline") - ) - .ReturnsAsync(Array.Empty()); + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/statuses/home_timeline", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -132,22 +141,24 @@ public async Task StatusesHomeTimeline_Test() [Fact] public async Task StatusesMentionsTimeline_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("statuses/mentions_timeline.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("statuses/mentions_timeline.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - }, - "/statuses/mentions_timeline") - ) - .ReturnsAsync(Array.Empty()); + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/statuses/mentions_timeline", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -160,24 +171,26 @@ public async Task StatusesMentionsTimeline_Test() [Fact] public async Task StatusesUserTimeline_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("statuses/user_timeline.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("statuses/user_timeline.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "screen_name", "twitterapi" }, - { "include_rts", "true" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - }, - "/statuses/user_timeline") - ) - .ReturnsAsync(Array.Empty()); + ["screen_name"] = "twitterapi", + ["include_rts"] = "true", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/statuses/user_timeline", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -190,20 +203,22 @@ public async Task StatusesUserTimeline_Test() [Fact] public async Task StatusesShow_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("statuses/show.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("statuses/show.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "id", "100" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/statuses/show/:id") - ) - .ReturnsAsync(new TwitterStatus { Id = 100L }); + ["id"] = "100", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/statuses/show/:id", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterStatus()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -216,21 +231,22 @@ public async Task StatusesShow_Test() [Fact] public async Task StatusesLookup_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("statuses/lookup.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("statuses/lookup.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "id", "100,200" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/statuses/lookup" - ) - ) - .ReturnsAsync(Array.Empty()); + ["id"] = "100,200", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/statuses/lookup", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -353,25 +369,27 @@ await twitterApi.StatusesRetweet(new("100")) [Fact] public async Task SearchTweets_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("search/tweets.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("search/tweets.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "q", "from:twitterapi" }, - { "result_type", "recent" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "lang", "en" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - }, - "/search/tweets") - ) - .ReturnsAsync(new TwitterSearchResult()); + ["q"] = "from:twitterapi", + ["result_type"] = "recent", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["lang"] = "en", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/search/tweets", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterSearchResult()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -384,19 +402,21 @@ public async Task SearchTweets_Test() [Fact] public async Task ListsOwnerships_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/ownerships.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/ownerships.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "screen_name", "twitterapi" }, - { "cursor", "-1" }, - { "count", "100" }, - }, - "/lists/ownerships") - ) - .ReturnsAsync(new TwitterLists()); + ["screen_name"] = "twitterapi", + ["cursor"] = "-1", + ["count"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/ownerships", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterLists()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -409,19 +429,21 @@ public async Task ListsOwnerships_Test() [Fact] public async Task ListsSubscriptions_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/subscriptions.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/subscriptions.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "screen_name", "twitterapi" }, - { "cursor", "-1" }, - { "count", "100" }, - }, - "/lists/subscriptions") - ) - .ReturnsAsync(new TwitterLists()); + ["screen_name"] = "twitterapi", + ["cursor"] = "-1", + ["count"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/subscriptions", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterLists()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -434,20 +456,22 @@ public async Task ListsSubscriptions_Test() [Fact] public async Task ListsMemberships_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/memberships.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/memberships.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "screen_name", "twitterapi" }, - { "cursor", "-1" }, - { "count", "100" }, - { "filter_to_owned_lists", "true" }, - }, - "/lists/memberships") - ) - .ReturnsAsync(new TwitterLists()); + ["screen_name"] = "twitterapi", + ["cursor"] = "-1", + ["count"] = "100", + ["filter_to_owned_lists"] = "true", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/memberships", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterLists()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -531,24 +555,26 @@ await twitterApi.ListsDestroy(12345L) [Fact] public async Task ListsStatuses_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/statuses.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/statuses.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "list_id", "12345" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - { "include_rts", "true" }, - }, - "/lists/statuses") - ) - .ReturnsAsync(Array.Empty()); + ["list_id"] = "12345", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + ["include_rts"] = "true", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/statuses", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -561,21 +587,23 @@ public async Task ListsStatuses_Test() [Fact] public async Task ListsMembers_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/members.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/members.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "list_id", "12345" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "cursor", "-1" }, - }, - "/lists/members") - ) - .ReturnsAsync(new TwitterUsers()); + ["list_id"] = "12345", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["cursor"] = "-1", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/members", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -588,21 +616,23 @@ public async Task ListsMembers_Test() [Fact] public async Task ListsMembersShow_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("lists/members/show.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("lists/members/show.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "list_id", "12345" }, - { "screen_name", "twitterapi" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/lists/members/show") - ) - .ReturnsAsync(new TwitterUser()); + ["list_id"] = "12345", + ["screen_name"] = "twitterapi", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/lists/members/show", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterUser()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -667,18 +697,20 @@ await twitterApi.ListsMembersDestroy(12345L, "twitterapi") [Fact] public async Task DirectMessagesEventsList_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("direct_messages/events/list.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("direct_messages/events/list.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "count", "50" }, - { "cursor", "12345abcdefg" }, - }, - "/direct_messages/events/list") - ) - .ReturnsAsync(new TwitterMessageEventList()); + ["count"] = "50", + ["cursor"] = "12345abcdefg", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/direct_messages/events/list", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterMessageEventList()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -751,20 +783,22 @@ public async Task DirectMessagesEventsDestroy_Test() [Fact] public async Task UsersShow_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("users/show.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("users/show.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "screen_name", "twitterapi" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/users/show/:id") - ) - .ReturnsAsync(new TwitterUser { ScreenName = "twitterapi" }); + ["screen_name"] = "twitterapi", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/users/show/:id", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterUser()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -777,20 +811,22 @@ public async Task UsersShow_Test() [Fact] public async Task UsersLookup_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("users/lookup.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("users/lookup.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "user_id", "11111,22222" }, - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/users/lookup") - ) - .ReturnsAsync(Array.Empty()); + ["user_id"] = "11111,22222", + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/users/lookup", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -826,22 +862,24 @@ await twitterApi.UsersReportSpam(screenName: "twitterapi") [Fact] public async Task FavoritesList_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("favorites/list.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("favorites/list.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - { "count", "200" }, - { "max_id", "900" }, - { "since_id", "100" }, - }, - "/favorites/list") - ) - .ReturnsAsync(Array.Empty()); + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + ["count"] = "200", + ["max_id"] = "900", + ["since_id"] = "100", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/favorites/list", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterStatus()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -900,14 +938,20 @@ public async Task FavoritesDestroy_Test() [Fact] public async Task FriendshipsShow_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("friendships/show.json", UriKind.Relative), - new Dictionary { { "source_screen_name", "twitter" }, { "target_screen_name", "twitterapi" } }, - "/friendships/show") - ) - .ReturnsAsync(new TwitterFriendship()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("friendships/show.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["source_screen_name"] = "twitter", + ["target_screen_name"] = "twitterapi", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/friendships/show", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterFriendship()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -964,14 +1008,15 @@ await twitterApi.FriendshipsDestroy(screenName: "twitterapi") [Fact] public async Task NoRetweetIds_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("friendships/no_retweets/ids.json", UriKind.Relative), - null, - "/friendships/no_retweets/ids") - ) - .ReturnsAsync(Array.Empty()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("friendships/no_retweets/ids.json", UriKind.Relative), r.RequestUri); + Assert.Null(r.Query); + Assert.Equal("/friendships/no_retweets/ids", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(Array.Empty()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -984,14 +1029,19 @@ public async Task NoRetweetIds_Test() [Fact] public async Task FollowersIds_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("followers/ids.json", UriKind.Relative), - new Dictionary { { "cursor", "-1" } }, - "/followers/ids") - ) - .ReturnsAsync(new TwitterIds()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("followers/ids.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["cursor"] = "-1", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/followers/ids", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterIds()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1004,14 +1054,19 @@ public async Task FollowersIds_Test() [Fact] public async Task MutesUsersIds_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("mutes/users/ids.json", UriKind.Relative), - new Dictionary { { "cursor", "-1" } }, - "/mutes/users/ids") - ) - .ReturnsAsync(new TwitterIds()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("mutes/users/ids.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["cursor"] = "-1", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/mutes/users/ids", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterIds()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1024,14 +1079,19 @@ public async Task MutesUsersIds_Test() [Fact] public async Task BlocksIds_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("blocks/ids.json", UriKind.Relative), - new Dictionary { { "cursor", "-1" } }, - "/blocks/ids") - ) - .ReturnsAsync(new TwitterIds()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("blocks/ids.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + ["cursor"] = "-1", + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/blocks/ids", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterIds()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1090,23 +1150,25 @@ await twitterApi.BlocksDestroy(screenName: "twitterapi") [Fact] public async Task AccountVerifyCredentials_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("account/verify_credentials.json", UriKind.Relative), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("account/verify_credentials.json", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary { - { "include_entities", "true" }, - { "include_ext_alt_text", "true" }, - { "tweet_mode", "extended" }, - }, - "/account/verify_credentials") - ) - .ReturnsAsync(new TwitterUser - { - Id = 100L, - ScreenName = "opentween", - }); + { "include_entities", "true" }, + { "include_ext_alt_text", "true" }, + { "tweet_mode", "extended" }, + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/account/verify_credentials", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterUser + { + Id = 100L, + ScreenName = "opentween", + }) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1182,14 +1244,15 @@ await twitterApi.AccountUpdateProfileImage(media) [Fact] public async Task ApplicationRateLimitStatus_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("application/rate_limit_status.json", UriKind.Relative), - null, - "/application/rate_limit_status") - ) - .ReturnsAsync(new TwitterRateLimits()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("application/rate_limit_status.json", UriKind.Relative), r.RequestUri); + Assert.Null(r.Query); + Assert.Equal("/application/rate_limit_status", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterRateLimits()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1202,14 +1265,15 @@ public async Task ApplicationRateLimitStatus_Test() [Fact] public async Task Configuration_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("help/configuration.json", UriKind.Relative), - null, - "/help/configuration") - ) - .ReturnsAsync(new TwitterConfiguration()); + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("help/configuration.json", UriKind.Relative), r.RequestUri); + Assert.Null(r.Query); + Assert.Equal("/help/configuration", r.EndpointName); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterConfiguration()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; @@ -1301,18 +1365,19 @@ await twitterApi.MediaUploadFinalize(mediaId: 11111L) [Fact] public async Task MediaUploadStatus_Test() { - var mock = new Mock(); - mock.Setup(x => - x.GetAsync( - new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute), - new Dictionary + var mock = this.CreateApiConnectionMock( + r => + { + Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri); + var expectedQuery = new Dictionary { - { "command", "STATUS" }, - { "media_id", "11111" }, - }, - null) - ) - .ReturnsAsync(new TwitterUploadMediaResult()); + ["command"] = "STATUS", + ["media_id"] = "11111", + }; + Assert.Equal(expectedQuery, r.Query); + }, + JsonUtils.SerializeJsonByDataContract(new TwitterUploadMediaResult()) + ); using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; diff --git a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs index a2b00fd96..62e0fedab 100644 --- a/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/TwitterV2/GetTimelineRequestTest.cs @@ -21,6 +21,8 @@ using System; using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Moq; using OpenTween.Api.DataModel; @@ -32,23 +34,36 @@ namespace OpenTween.Api.TwitterV2 public class GetTimelineRequestTest { [Fact] - public async Task StatusesMentionsTimeline_Test() + public async Task Send_Test() { - var mock = new Mock(); + Func verifyRequest = r => + { + Assert.Equal(new("/2/users/100/timelines/reverse_chronological", UriKind.Relative), r.RequestUri); + var expectedQuery = new Dictionary + { + { "tweet.fields", "id" }, + { "max_results", "200" }, + { "until_id", "900" }, + { "since_id", "100" }, + }; + Assert.Equal(expectedQuery, r.Query); + Assert.Equal("/2/users/:id/timelines/reverse_chronological", r.EndpointName); + return true; + }; + + using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent( + JsonUtils.SerializeJsonByDataContract(new TwitterV2TweetIds()) + ), + }; + var mock = new Mock(); mock.Setup(x => - x.GetAsync( - new Uri("/2/users/100/timelines/reverse_chronological", UriKind.Relative), - new Dictionary - { - { "tweet.fields", "id" }, - { "max_results", "200" }, - { "until_id", "900" }, - { "since_id", "100" }, - }, - "/2/users/:id/timelines/reverse_chronological" + x.SendAsync( + It.Is(r => verifyRequest(r)) ) ) - .ReturnsAsync(new TwitterV2TweetIds()); + .ReturnsAsync(new ApiResponse(responseMessage)); var request = new GetTimelineRequest(userId: 100L) { diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index d5720327a..7386e08e7 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -62,9 +62,8 @@ public void Initialize(ITwitterCredential credential, long userId, string screen this.CurrentScreenName = screenName; } - public Task StatusesHomeTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) + public async Task StatusesHomeTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) { - var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative); var param = new Dictionary { ["include_entities"] = "true", @@ -79,12 +78,22 @@ public Task StatusesHomeTimeline(int? count = null, TwitterStat if (sinceId != null) param["since_id"] = sinceId.Id; - return this.Connection.GetAsync(endpoint, param, "/statuses/home_timeline"); + var request = new GetRequest + { + RequestUri = new("statuses/home_timeline.json", UriKind.Relative), + Query = param, + EndpointName = "/statuses/home_timeline", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task StatusesMentionsTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) + public async Task StatusesMentionsTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) { - var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative); var param = new Dictionary { ["include_entities"] = "true", @@ -99,12 +108,22 @@ public Task StatusesMentionsTimeline(int? count = null, Twitter if (sinceId != null) param["since_id"] = sinceId.Id; - return this.Connection.GetAsync(endpoint, param, "/statuses/mentions_timeline"); + var request = new GetRequest + { + RequestUri = new("statuses/mentions_timeline.json", UriKind.Relative), + Query = param, + EndpointName = "/statuses/mentions_timeline", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task StatusesUserTimeline(string screenName, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) + public async Task StatusesUserTimeline(string screenName, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) { - var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative); var param = new Dictionary { ["screen_name"] = screenName, @@ -121,35 +140,62 @@ public Task StatusesUserTimeline(string screenName, int? count if (sinceId != null) param["since_id"] = sinceId.Id; - return this.Connection.GetAsync(endpoint, param, "/statuses/user_timeline"); + var request = new GetRequest + { + RequestUri = new("statuses/user_timeline.json", UriKind.Relative), + Query = param, + EndpointName = "/statuses/user_timeline", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task StatusesShow(TwitterStatusId statusId) + public async Task StatusesShow(TwitterStatusId statusId) { - var endpoint = new Uri("statuses/show.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["id"] = statusId.Id, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("statuses/show.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = statusId.Id, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/statuses/show/:id", }; - return this.Connection.GetAsync(endpoint, param, "/statuses/show/:id"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task StatusesLookup(IReadOnlyList statusIds) + public async Task StatusesLookup(IReadOnlyList statusIds) { - var endpoint = new Uri("statuses/lookup.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["id"] = string.Join(",", statusIds), - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("statuses/lookup.json", UriKind.Relative), + Query = new Dictionary + { + ["id"] = string.Join(",", statusIds), + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/statuses/lookup", }; - return this.Connection.GetAsync(endpoint, param, "/statuses/lookup"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> StatusesUpdate( @@ -228,9 +274,8 @@ public async Task> StatusesRetweet(TwitterStatusId statu return response.ReadAsLazyJson(); } - public Task SearchTweets(string query, string? lang = null, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) + public async Task SearchTweets(string query, string? lang = null, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) { - var endpoint = new Uri("search/tweets.json", UriKind.Relative); var param = new Dictionary { ["q"] = query, @@ -249,12 +294,22 @@ public Task SearchTweets(string query, string? lang = null, if (sinceId != null) param["since_id"] = sinceId.Id; - return this.Connection.GetAsync(endpoint, param, "/search/tweets"); + var request = new GetRequest + { + RequestUri = new("search/tweets.json", UriKind.Relative), + Query = param, + EndpointName = "/search/tweets", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task ListsOwnerships(string screenName, long? cursor = null, int? count = null) + public async Task ListsOwnerships(string screenName, long? cursor = null, int? count = null) { - var endpoint = new Uri("lists/ownerships.json", UriKind.Relative); var param = new Dictionary { ["screen_name"] = screenName, @@ -265,12 +320,22 @@ public Task ListsOwnerships(string screenName, long? cursor = null if (count != null) param["count"] = count.ToString(); - return this.Connection.GetAsync(endpoint, param, "/lists/ownerships"); + var request = new GetRequest + { + RequestUri = new("lists/ownerships.json", UriKind.Relative), + Query = param, + EndpointName = "/lists/ownerships", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task ListsSubscriptions(string screenName, long? cursor = null, int? count = null) + public async Task ListsSubscriptions(string screenName, long? cursor = null, int? count = null) { - var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative); var param = new Dictionary { ["screen_name"] = screenName, @@ -281,12 +346,22 @@ public Task ListsSubscriptions(string screenName, long? cursor = n if (count != null) param["count"] = count.ToString(); - return this.Connection.GetAsync(endpoint, param, "/lists/subscriptions"); + var request = new GetRequest + { + RequestUri = new("lists/subscriptions.json", UriKind.Relative), + Query = param, + EndpointName = "/lists/subscriptions", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null) + public async Task ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null) { - var endpoint = new Uri("lists/memberships.json", UriKind.Relative); var param = new Dictionary { ["screen_name"] = screenName, @@ -299,7 +374,18 @@ public Task ListsMemberships(string screenName, long? cursor = nul if (filterToOwnedLists != null) param["filter_to_owned_lists"] = filterToOwnedLists.Value ? "true" : "false"; - return this.Connection.GetAsync(endpoint, param, "/lists/memberships"); + var request = new GetRequest + { + RequestUri = new("lists/memberships.json", UriKind.Relative), + Query = param, + EndpointName = "/lists/memberships", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> ListsCreate(string name, string? description = null, bool? @private = null) @@ -369,9 +455,8 @@ public async Task> ListsDestroy(long listId) return response.ReadAsLazyJson(); } - public Task ListsStatuses(long listId, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null, bool? includeRTs = null) + public async Task ListsStatuses(long listId, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null, bool? includeRTs = null) { - var endpoint = new Uri("lists/statuses.json", UriKind.Relative); var param = new Dictionary { ["list_id"] = listId.ToString(), @@ -389,12 +474,22 @@ public Task ListsStatuses(long listId, int? count = null, Twitt if (includeRTs != null) param["include_rts"] = includeRTs.Value ? "true" : "false"; - return this.Connection.GetAsync(endpoint, param, "/lists/statuses"); + var request = new GetRequest + { + RequestUri = new("lists/statuses.json", UriKind.Relative), + Query = param, + EndpointName = "/lists/statuses", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task ListsMembers(long listId, long? cursor = null) + public async Task ListsMembers(long listId, long? cursor = null) { - var endpoint = new Uri("lists/members.json", UriKind.Relative); var param = new Dictionary { ["list_id"] = listId.ToString(), @@ -406,22 +501,41 @@ public Task ListsMembers(long listId, long? cursor = null) if (cursor != null) param["cursor"] = cursor.ToString(); - return this.Connection.GetAsync(endpoint, param, "/lists/members"); + var request = new GetRequest + { + RequestUri = new("lists/members.json", UriKind.Relative), + Query = param, + EndpointName = "/lists/members", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task ListsMembersShow(long listId, string screenName) + public async Task ListsMembersShow(long listId, string screenName) { - var endpoint = new Uri("lists/members/show.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["list_id"] = listId.ToString(), - ["screen_name"] = screenName, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("lists/members/show.json", UriKind.Relative), + Query = new Dictionary + { + ["list_id"] = listId.ToString(), + ["screen_name"] = screenName, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/lists/members/show", }; - return this.Connection.GetAsync(endpoint, param, "/lists/members/show"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> ListsMembersCreate(long listId, string screenName) @@ -466,9 +580,8 @@ public async Task> ListsMembersDestroy(long listId, string return response.ReadAsLazyJson(); } - public Task DirectMessagesEventsList(int? count = null, string? cursor = null) + public async Task DirectMessagesEventsList(int? count = null, string? cursor = null) { - var endpoint = new Uri("direct_messages/events/list.json", UriKind.Relative); var param = new Dictionary(); if (count != null) @@ -476,7 +589,18 @@ public Task DirectMessagesEventsList(int? count = null, if (cursor != null) param["cursor"] = cursor; - return this.Connection.GetAsync(endpoint, param, "/direct_messages/events/list"); + var request = new GetRequest + { + RequestUri = new("direct_messages/events/list.json", UriKind.Relative), + Query = param, + EndpointName = "/direct_messages/events/list", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null) @@ -538,32 +662,48 @@ await this.Connection.SendAsync(request) .ConfigureAwait(false); } - public Task UsersShow(string screenName) + public async Task UsersShow(string screenName) { - var endpoint = new Uri("users/show.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["screen_name"] = screenName, - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("users/show.json", UriKind.Relative), + Query = new Dictionary + { + ["screen_name"] = screenName, + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/users/show/:id", }; - return this.Connection.GetAsync(endpoint, param, "/users/show/:id"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task UsersLookup(IReadOnlyList userIds) + public async Task UsersLookup(IReadOnlyList userIds) { - var endpoint = new Uri("users/lookup.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["user_id"] = string.Join(",", userIds), - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("users/lookup.json", UriKind.Relative), + Query = new Dictionary + { + ["user_id"] = string.Join(",", userIds), + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/users/lookup", }; - return this.Connection.GetAsync(endpoint, param, "/users/lookup"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> UsersReportSpam(string screenName) @@ -584,9 +724,8 @@ public async Task> UsersReportSpam(string screenName) return response.ReadAsLazyJson(); } - public Task FavoritesList(int? count = null, long? maxId = null, long? sinceId = null) + public async Task FavoritesList(int? count = null, long? maxId = null, long? sinceId = null) { - var endpoint = new Uri("favorites/list.json", UriKind.Relative); var param = new Dictionary { ["include_entities"] = "true", @@ -601,7 +740,18 @@ public Task FavoritesList(int? count = null, long? maxId = null if (sinceId != null) param["since_id"] = sinceId.ToString(); - return this.Connection.GetAsync(endpoint, param, "/favorites/list"); + var request = new GetRequest + { + RequestUri = new("favorites/list.json", UriKind.Relative), + Query = param, + EndpointName = "/favorites/list", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> FavoritesCreate(TwitterStatusId statusId) @@ -640,16 +790,24 @@ public async Task> FavoritesDestroy(TwitterStatusId stat return response.ReadAsLazyJson(); } - public Task FriendshipsShow(string sourceScreenName, string targetScreenName) + public async Task FriendshipsShow(string sourceScreenName, string targetScreenName) { - var endpoint = new Uri("friendships/show.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["source_screen_name"] = sourceScreenName, - ["target_screen_name"] = targetScreenName, + RequestUri = new("friendships/show.json", UriKind.Relative), + Query = new Dictionary + { + ["source_screen_name"] = sourceScreenName, + ["target_screen_name"] = targetScreenName, + }, + EndpointName = "/friendships/show", }; - return this.Connection.GetAsync(endpoint, param, "/friendships/show"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> FriendshipsCreate(string screenName) @@ -686,44 +844,82 @@ public async Task> FriendshipsDestroy(string screenN return response.ReadAsLazyJson(); } - public Task NoRetweetIds() + public async Task NoRetweetIds() { - var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("friendships/no_retweets/ids.json", UriKind.Relative), + EndpointName = "/friendships/no_retweets/ids", + }; - return this.Connection.GetAsync(endpoint, null, "/friendships/no_retweets/ids"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task FollowersIds(long? cursor = null) + public async Task FollowersIds(long? cursor = null) { - var endpoint = new Uri("followers/ids.json", UriKind.Relative); var param = new Dictionary(); if (cursor != null) param["cursor"] = cursor.ToString(); - return this.Connection.GetAsync(endpoint, param, "/followers/ids"); + var request = new GetRequest + { + RequestUri = new("followers/ids.json", UriKind.Relative), + Query = param, + EndpointName = "/followers/ids", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task MutesUsersIds(long? cursor = null) + public async Task MutesUsersIds(long? cursor = null) { - var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative); var param = new Dictionary(); if (cursor != null) param["cursor"] = cursor.ToString(); - return this.Connection.GetAsync(endpoint, param, "/mutes/users/ids"); + var request = new GetRequest + { + RequestUri = new("mutes/users/ids.json", UriKind.Relative), + Query = param, + EndpointName = "/mutes/users/ids", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task BlocksIds(long? cursor = null) + public async Task BlocksIds(long? cursor = null) { - var endpoint = new Uri("blocks/ids.json", UriKind.Relative); var param = new Dictionary(); if (cursor != null) param["cursor"] = cursor.ToString(); - return this.Connection.GetAsync(endpoint, param, "/blocks/ids"); + var request = new GetRequest + { + RequestUri = new("blocks/ids.json", UriKind.Relative), + Query = param, + EndpointName = "/blocks/ids", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> BlocksCreate(string screenName) @@ -764,15 +960,22 @@ public async Task> BlocksDestroy(string screenName) public async Task AccountVerifyCredentials() { - var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative); - var param = new Dictionary + var request = new GetRequest { - ["include_entities"] = "true", - ["include_ext_alt_text"] = "true", - ["tweet_mode"] = "extended", + RequestUri = new("account/verify_credentials.json", UriKind.Relative), + Query = new Dictionary + { + ["include_entities"] = "true", + ["include_ext_alt_text"] = "true", + ["tweet_mode"] = "extended", + }, + EndpointName = "/account/verify_credentials", }; - var user = await this.Connection.GetAsync(endpoint, param, "/account/verify_credentials") + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + var user = await response.ReadAsJson() .ConfigureAwait(false); this.CurrentUserId = user.Id; @@ -840,18 +1043,34 @@ public async Task> AccountUpdateProfileImage(IMediaItem im return response.ReadAsLazyJson(); } - public Task ApplicationRateLimitStatus() + public async Task ApplicationRateLimitStatus() { - var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("application/rate_limit_status.json", UriKind.Relative), + EndpointName = "/application/rate_limit_status", + }; + + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); - return this.Connection.GetAsync(endpoint, null, "/application/rate_limit_status"); + return await response.ReadAsJson() + .ConfigureAwait(false); } - public Task Configuration() + public async Task Configuration() { - var endpoint = new Uri("help/configuration.json", UriKind.Relative); + var request = new GetRequest + { + RequestUri = new("help/configuration.json", UriKind.Relative), + EndpointName = "/help/configuration", + }; - return this.Connection.GetAsync(endpoint, null, "/help/configuration"); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null) @@ -918,16 +1137,23 @@ public async Task> MediaUploadFinalize(long m return response.ReadAsLazyJson(); } - public Task MediaUploadStatus(long mediaId) + public async Task MediaUploadStatus(long mediaId) { - var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json"); - var param = new Dictionary + var request = new GetRequest { - ["command"] = "STATUS", - ["media_id"] = mediaId.ToString(), + RequestUri = new("https://upload.twitter.com/1.1/media/upload.json"), + Query = new Dictionary + { + ["command"] = "STATUS", + ["media_id"] = mediaId.ToString(), + }, }; - return this.Connection.GetAsync(endpoint, param, endpointName: null); + using var response = await this.Connection.SendAsync(request) + .ConfigureAwait(false); + + return await response.ReadAsJson() + .ConfigureAwait(false); } public async Task MediaMetadataCreate(long mediaId, string altText) diff --git a/OpenTween/Api/TwitterV2/GetTimelineRequest.cs b/OpenTween/Api/TwitterV2/GetTimelineRequest.cs index 677d167ae..24faa6b23 100644 --- a/OpenTween/Api/TwitterV2/GetTimelineRequest.cs +++ b/OpenTween/Api/TwitterV2/GetTimelineRequest.cs @@ -69,12 +69,20 @@ private Dictionary CreateParameters() return param; } - public Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var uri = this.CreateEndpointUri(); - var param = this.CreateParameters(); + var request = new GetRequest + { + RequestUri = this.CreateEndpointUri(), + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); - return apiConnection.GetAsync(uri, param, EndpointName); + return await response.ReadAsJson() + .ConfigureAwait(false); } } } From 1cfe708c4516f1198e44fabe0da6fe91f17f6737 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 02:40:08 +0900 Subject: [PATCH 68/76] =?UTF-8?q?TwitterApiConnection.GetAsync=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Connection/TwitterApiConnectionTest.cs | 75 ------------------- OpenTween/Connection/IApiConnectionLegacy.cs | 1 - OpenTween/Connection/TwitterApiConnection.cs | 16 ---- 3 files changed, 92 deletions(-) diff --git a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs index 6c683cabe..8960fce13 100644 --- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs +++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs @@ -52,81 +52,6 @@ private void MyCommonSetup() MyCommon.EntryAssembly = mockAssembly.Object; } - [Fact] - public async Task GetAsync_Test() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(x => - { - Assert.Equal(HttpMethod.Get, x.Method); - Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json", - x.RequestUri.GetLeftPart(UriPartial.Path)); - - var query = HttpUtility.ParseQueryString(x.RequestUri.Query); - - Assert.Equal("1111", query["aaaa"]); - Assert.Equal("2222", query["bbbb"]); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("hoge/tetete.json", UriKind.Relative); - var param = new Dictionary - { - ["aaaa"] = "1111", - ["bbbb"] = "2222", - }; - - var result = await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete"); - Assert.Equal("hogehoge", result); - - Assert.Equal(0, mockHandler.QueueCount); - } - - [Fact] - public async Task GetAsync_AbsoluteUriTest() - { - using var mockHandler = new HttpMessageHandlerMock(); - using var http = new HttpClient(mockHandler); - using var apiConnection = new TwitterApiConnection(); - apiConnection.Http = http; - - mockHandler.Enqueue(x => - { - Assert.Equal(HttpMethod.Get, x.Method); - Assert.Equal("http://example.com/hoge/tetete.json", - x.RequestUri.GetLeftPart(UriPartial.Path)); - - var query = HttpUtility.ParseQueryString(x.RequestUri.Query); - - Assert.Equal("1111", query["aaaa"]); - Assert.Equal("2222", query["bbbb"]); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("\"hogehoge\""), - }; - }); - - var endpoint = new Uri("http://example.com/hoge/tetete.json", UriKind.Absolute); - var param = new Dictionary - { - ["aaaa"] = "1111", - ["bbbb"] = "2222", - }; - - await apiConnection.GetAsync(endpoint, param, endpointName: "/hoge/tetete"); - - Assert.Equal(0, mockHandler.QueueCount); - } - [Fact] public async Task SendAsync_Test() { diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs index 67618ac19..2c2db7f88 100644 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ b/OpenTween/Connection/IApiConnectionLegacy.cs @@ -32,6 +32,5 @@ namespace OpenTween.Connection { public interface IApiConnectionLegacy : IApiConnection, IDisposable { - Task GetAsync(Uri uri, IDictionary? param, string? endpointName); } } diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index f2b3c28df..b5e2d1f60 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -121,22 +121,6 @@ await TwitterApiConnection.CheckStatusCode(responseMessage) } } - public async Task GetAsync(Uri uri, IDictionary? param, string? endpointName) - { - var request = new GetRequest - { - RequestUri = uri, - Query = param, - EndpointName = endpointName, - }; - - using var response = await this.SendAsync(request) - .ConfigureAwait(false); - - return await response.ReadAsJson() - .ConfigureAwait(false); - } - /// /// 指定されたエンドポイントがレートリミット規制中であれば例外を発生させる /// From cc3494ca03050d3113ce92d9abcc1d63c06f8de3 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 02:41:49 +0900 Subject: [PATCH 69/76] =?UTF-8?q?IApiConnectionLegacy=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 6 ++-- OpenTween/Api/TwitterApi.cs | 4 +-- OpenTween/Connection/IApiConnectionLegacy.cs | 36 -------------------- OpenTween/Connection/TwitterApiConnection.cs | 2 +- 4 files changed, 6 insertions(+), 42 deletions(-) delete mode 100644 OpenTween/Connection/IApiConnectionLegacy.cs diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index f19ca8499..ed2117170 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -79,11 +79,11 @@ public void Initialize_Test() Assert.Equal("foobar", twitterApi.CurrentScreenName); } - private Mock CreateApiConnectionMock(Action verifyRequest) + private Mock CreateApiConnectionMock(Action verifyRequest) where T : IHttpRequest => this.CreateApiConnectionMock(verifyRequest, ""); - private Mock CreateApiConnectionMock(Action verifyRequest, string responseText) + private Mock CreateApiConnectionMock(Action verifyRequest, string responseText) where T : IHttpRequest { Func verifyRequestWrapper = r => @@ -97,7 +97,7 @@ private Mock CreateApiConnectionMock(Action verifyRe { Content = new StringContent(responseText), }; - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => x.SendAsync( It.Is(r => verifyRequestWrapper(r)) diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 7386e08e7..f1186b1e7 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -41,9 +41,9 @@ public sealed class TwitterApi : IDisposable public string CurrentScreenName { get; private set; } = ""; - public IApiConnectionLegacy Connection => this.ApiConnection; + public IApiConnection Connection => this.ApiConnection; - internal IApiConnectionLegacy ApiConnection; + internal IApiConnection ApiConnection; public APIAuthType AuthType { get; private set; } = APIAuthType.None; diff --git a/OpenTween/Connection/IApiConnectionLegacy.cs b/OpenTween/Connection/IApiConnectionLegacy.cs deleted file mode 100644 index 2c2db7f88..000000000 --- a/OpenTween/Connection/IApiConnectionLegacy.cs +++ /dev/null @@ -1,36 +0,0 @@ -// OpenTween - Client of Twitter -// Copyright (c) 2016 kim_upsilon (@kim_upsilon) -// All rights reserved. -// -// This file is part of OpenTween. -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program. If not, see , or write to -// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, -// Boston, MA 02110-1301, USA. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace OpenTween.Connection -{ - public interface IApiConnectionLegacy : IApiConnection, IDisposable - { - } -} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index b5e2d1f60..410359737 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -39,7 +39,7 @@ namespace OpenTween.Connection { - public class TwitterApiConnection : IApiConnection, IApiConnectionLegacy, IDisposable + public class TwitterApiConnection : IApiConnection, IDisposable { public static Uri RestApiBase { get; set; } = new("https://api.twitter.com/1.1/"); From 4152707706d47a9caca3ae994b70a953857256f9 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 13 Dec 2023 03:01:42 +0900 Subject: [PATCH 70/76] =?UTF-8?q?LazyJson=E3=81=AE=E4=B8=8D=E8=A6=81?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F=E3=82=B3=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=83=A9=E3=82=AF=E3=82=BF=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Connection/LazyJson.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/OpenTween/Connection/LazyJson.cs b/OpenTween/Connection/LazyJson.cs index 2f0bc80e0..51a156cca 100644 --- a/OpenTween/Connection/LazyJson.cs +++ b/OpenTween/Connection/LazyJson.cs @@ -46,14 +46,6 @@ public sealed class LazyJson : IDisposable public LazyJson(HttpResponseMessage response) => this.Response = response; - internal LazyJson(T instance) - { - this.Response = null; - - this.instance = instance; - this.completed = true; - } - public async Task LoadJsonAsync() { if (this.completed) @@ -80,12 +72,6 @@ public void Dispose() => this.Response?.Dispose(); } - public static class LazyJson - { - public static LazyJson Create(T instance) - => new(instance); - } - public static class LazyJsonTaskExtension { public static async Task IgnoreResponse(this Task> task) From d2c6200558a5fc7bf14531daeff80388a6928270 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 15 Dec 2023 00:39:18 +0900 Subject: [PATCH 71/76] =?UTF-8?q?TweenMain.FormatStatusText=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E3=81=99=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/TweenMainTest.cs | 120 ++++++++++++++++++++++++++++++- OpenTween/Tween.cs | 2 +- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index ea14617c6..8127d105e 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -24,6 +24,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Windows.Forms; using OpenTween.Api; using OpenTween.Api.DataModel; @@ -38,8 +39,11 @@ namespace OpenTween { public class TweenMainTest { - [WinFormsFact] - public void Initialize_Test() + private record TestContext( + SettingManager Settings + ); + + private void UsingTweenMain(Action func) { var settings = new SettingManager(""); var tabinfo = new TabInformations(); @@ -50,6 +54,118 @@ public void Initialize_Test() var thumbnailGenerator = new ThumbnailGenerator(new(autoupdate: false)); using var tweenMain = new TweenMain(settings, tabinfo, twitter, imageCache, iconAssets, thumbnailGenerator); + var context = new TestContext(settings); + + func(tweenMain, context); + } + + [WinFormsFact] + public void Initialize_Test() + => this.UsingTweenMain((_, _) => { }); + + [WinFormsFact] + public void FormatStatusText_NewLineTest() + { + this.UsingTweenMain((tweenMain, _) => + { + Assert.Equal("aaa\nbbb", tweenMain.FormatStatusText("aaa\r\nbbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_NewLineInDMTest() + { + this.UsingTweenMain((tweenMain, _) => + { + // DM にも適用する + Assert.Equal("D opentween aaa\nbbb", tweenMain.FormatStatusText("D opentween aaa\r\nbbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_ReplaceFullwidthSpace_EnabledTest() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.WideSpaceConvert = true; + Assert.Equal("aaa bbb", tweenMain.FormatStatusText("aaa bbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_ReplaceFullwidthSpaceInDM_EnabledTest() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.WideSpaceConvert = true; + + // DM にも適用する + Assert.Equal("D opentween aaa bbb", tweenMain.FormatStatusText("D opentween aaa bbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_ReplaceFullwidthSpace_DisabledTest() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.WideSpaceConvert = false; + Assert.Equal("aaa bbb", tweenMain.FormatStatusText("aaa bbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_UseRecommendedFooter_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Local.UseRecommendStatus = true; + Assert.Matches(new Regex(@"^aaa \[TWNv\d+\]$"), tweenMain.FormatStatusText("aaa")); + }); + } + + [WinFormsFact] + public void FormatStatusText_CustomFooterText_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Local.StatusText = "foo"; + Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa")); + }); + } + + [WinFormsFact] + public void FormatStatusText_DisableFooterIfSendingDM_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Local.StatusText = "foo"; + + // DM の場合はフッターを無効化する + Assert.Equal("D opentween aaa", tweenMain.FormatStatusText("D opentween aaa")); + }); + } + + [WinFormsFact] + public void FormatStatusText_DisableFooterIfContainsUnofficialRT_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Local.StatusText = "foo"; + + // 非公式 RT を含む場合はフッターを無効化する + Assert.Equal("aaa RT @foo: bbb", tweenMain.FormatStatusText("aaa RT @foo: bbb")); + }); + } + + [WinFormsFact] + public void FormatStatusText_PreventSmsCommand_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + // 「D+」などから始まる文字列をツイートしようとすると SMS コマンドと誤認されてエラーが返される問題の回避策 + Assert.Equal("\u200bd+aaaa", tweenMain.FormatStatusText("d+aaaa")); + }); } [Fact] diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 0d2de5d64..879f13fa4 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -3531,7 +3531,7 @@ private string FormatStatusTextExtended(string statusText, out long[] autoPopula /// /// ツイート投稿前のフッター付与などの前処理を行います /// - private string FormatStatusText(string statusText) + internal string FormatStatusText(string statusText) { statusText = statusText.Replace("\r\n", "\n"); From 45b33f4b1a348b51312329b7239e3f6b1fa7b213 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 15 Dec 2023 03:35:50 +0900 Subject: [PATCH 72/76] =?UTF-8?q?=E3=80=8CURL=E3=81=8B=E3=82=89=E3=81=AE?= =?UTF-8?q?=E5=85=A8=E8=A7=92=E6=96=87=E5=AD=97=E5=88=97=E3=81=AE=E5=88=87?= =?UTF-8?q?=E3=82=8A=E9=9B=A2=E3=81=97=E3=80=8D=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=B1=E3=83=BC=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/TweenMainTest.cs | 20 ++++++++++++++++++++ OpenTween/Tween.cs | 12 +++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index 8127d105e..01e7f73fa 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -82,6 +82,26 @@ public void FormatStatusText_NewLineInDMTest() }); } + [WinFormsFact] + public void FormatStatusText_SeparateUrlAndFullwidthCharacter_EnabledTest() + { + this.UsingTweenMain((tweenMain, context) => + { + tweenMain.SeparateUrlAndFullwidthCharacter = true; + Assert.Equal("https://example.com/ あああ", tweenMain.FormatStatusText("https://example.com/あああ")); + }); + } + + [WinFormsFact] + public void FormatStatusText_SeparateUrlAndFullwidthCharacter_DisabledTest() + { + this.UsingTweenMain((tweenMain, context) => + { + tweenMain.SeparateUrlAndFullwidthCharacter = false; + Assert.Equal("https://example.com/あああ", tweenMain.FormatStatusText("https://example.com/あああ")); + }); + } + [WinFormsFact] public void FormatStatusText_ReplaceFullwidthSpace_EnabledTest() { diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 879f13fa4..8ae352ba5 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -208,7 +208,9 @@ public partial class TweenMain : OTBaseForm private readonly DebounceTimer saveConfigDebouncer; private readonly string recommendedStatusFooter; - private bool urlMultibyteSplit = false; + + internal bool SeparateUrlAndFullwidthCharacter { get; set; } = false; + private bool preventSmsCommand = true; // URL短縮のUndo用 @@ -3535,7 +3537,7 @@ internal string FormatStatusText(string statusText) { statusText = statusText.Replace("\r\n", "\n"); - if (this.urlMultibyteSplit) + if (this.SeparateUrlAndFullwidthCharacter) { // URLと全角文字の切り離し statusText = Regex.Replace(statusText, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+", "$& "); @@ -8193,7 +8195,7 @@ private void MenuItemHelp_DropDownOpening(object sender, EventArgs e) } private void UrlMultibyteSplitMenuItem_CheckedChanged(object sender, EventArgs e) - => this.urlMultibyteSplit = ((ToolStripMenuItem)sender).Checked; + => this.SeparateUrlAndFullwidthCharacter = ((ToolStripMenuItem)sender).Checked; private void PreventSmsCommandMenuItem_CheckedChanged(object sender, EventArgs e) => this.preventSmsCommand = ((ToolStripMenuItem)sender).Checked; @@ -8215,7 +8217,7 @@ private void FocusLockMenuItem_CheckedChanged(object sender, EventArgs e) private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e) { - this.UrlMultibyteSplitMenuItem.Checked = this.urlMultibyteSplit; + this.UrlMultibyteSplitMenuItem.Checked = this.SeparateUrlAndFullwidthCharacter; this.PreventSmsCommandMenuItem.Checked = this.preventSmsCommand; this.UrlAutoShortenMenuItem.Checked = this.settings.Common.UrlConvertAuto; this.IdeographicSpaceToSpaceMenuItem.Checked = this.settings.Common.WideSpaceConvert; @@ -8225,7 +8227,7 @@ private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e) private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e) { - this.UrlMultibyteSplitPullDownMenuItem.Checked = this.urlMultibyteSplit; + this.UrlMultibyteSplitPullDownMenuItem.Checked = this.SeparateUrlAndFullwidthCharacter; this.PreventSmsCommandPullDownMenuItem.Checked = this.preventSmsCommand; this.UrlAutoShortenPullDownMenuItem.Checked = this.settings.Common.UrlConvertAuto; this.IdeographicSpaceToSpacePullDownMenuItem.Checked = this.settings.Common.WideSpaceConvert; From 05968909a4be37cc42c341c0d69069778634b117 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 15 Dec 2023 02:09:08 +0900 Subject: [PATCH 73/76] =?UTF-8?q?MyCommon.IsKeyDown=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=AE=E5=BC=95=E6=95=B0=E3=81=AE=E5=BD=A2?= =?UTF-8?q?=E5=BC=8F=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/MyCommonTest.cs | 16 ++++++++-------- OpenTween/MyCommon.cs | 17 ++++------------- OpenTween/Tween.cs | 2 +- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/OpenTween.Tests/MyCommonTest.cs b/OpenTween.Tests/MyCommonTest.cs index 1261d4730..8ebb2a1bc 100644 --- a/OpenTween.Tests/MyCommonTest.cs +++ b/OpenTween.Tests/MyCommonTest.cs @@ -157,14 +157,14 @@ public void IsValidEmailTest(string email, bool expected) => Assert.Equal(expected, MyCommon.IsValidEmail(email)); [Theory] - [InlineData(Keys.Shift, new[] { Keys.Shift }, true)] - [InlineData(Keys.Shift, new[] { Keys.Control }, false)] - [InlineData(Keys.Control | Keys.Alt, new[] { Keys.Control }, true)] - [InlineData(Keys.Control | Keys.Alt, new[] { Keys.Alt }, true)] - [InlineData(Keys.Control | Keys.Alt, new[] { Keys.Control, Keys.Alt }, true)] - [InlineData(Keys.Control | Keys.Alt, new[] { Keys.Shift }, false)] - public void IsKeyDownTest(Keys modifierKeys, Keys[] checkKeys, bool expected) - => Assert.Equal(expected, MyCommon.IsKeyDownInternal(modifierKeys, checkKeys)); + [InlineData(Keys.Shift, Keys.Shift, true)] + [InlineData(Keys.Shift, Keys.Control, false)] + [InlineData(Keys.Control | Keys.Alt, Keys.Control, true)] + [InlineData(Keys.Control | Keys.Alt, Keys.Alt, true)] + [InlineData(Keys.Control | Keys.Alt, Keys.Control | Keys.Alt, true)] + [InlineData(Keys.Control | Keys.Alt, Keys.Shift, false)] + public void IsKeyDownTest(Keys modifierKeys, Keys checkKeys, bool expected) + => Assert.Equal(expected, MyCommon.IsKeyDown(modifierKeys, checkKeys)); [Fact] public void GetAssemblyNameTest() diff --git a/OpenTween/MyCommon.cs b/OpenTween/MyCommon.cs index e6b1f3d7c..0732c2e2b 100644 --- a/OpenTween/MyCommon.cs +++ b/OpenTween/MyCommon.cs @@ -757,20 +757,11 @@ public static bool IsValidEmail(string strIn) /// /// 状態を調べるキー /// で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。 - public static bool IsKeyDown(params Keys[] keys) - => MyCommon.IsKeyDownInternal(Control.ModifierKeys, keys); + public static bool IsKeyDown(Keys keys) + => MyCommon.IsKeyDown(Control.ModifierKeys, keys); - internal static bool IsKeyDownInternal(Keys modifierKeys, Keys[] targetKeys) - { - foreach (var key in targetKeys) - { - if ((modifierKeys & key) != key) - { - return false; - } - } - return true; - } + public static bool IsKeyDown(Keys modifierKeys, Keys targetKeys) + => (modifierKeys & targetKeys) == targetKeys; /// /// アプリケーションのアセンブリ名を取得します。 diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 8ae352ba5..00cef7aa5 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -8188,7 +8188,7 @@ private void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e) private void MenuItemHelp_DropDownOpening(object sender, EventArgs e) { - if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift)) + if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock | Keys.Control | Keys.Shift)) this.DebugModeToolStripMenuItem.Visible = true; else this.DebugModeToolStripMenuItem.Visible = false; From ccd1bb6d693e3714f1a1e26e8920dea429707dfd Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Fri, 15 Dec 2023 02:57:28 +0900 Subject: [PATCH 74/76] =?UTF-8?q?TweenMain.FormatStatusText=E3=81=A7?= =?UTF-8?q?=E4=BF=AE=E9=A3=BE=E3=82=AD=E3=83=BC=E3=81=8C=E5=BD=B1=E9=9F=BF?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/TweenMainTest.cs | 86 ++++++++++++++++++++++++++++++++ OpenTween/Tween.cs | 13 +++-- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index 01e7f73fa..8beba3b2c 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -178,6 +178,92 @@ public void FormatStatusText_DisableFooterIfContainsUnofficialRT_Test() }); } + [WinFormsFact] + public void FormatStatusText_DisableFooterIfPostByEnterAndPressedShiftKey_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = false; + context.Settings.Common.PostShiftEnter = false; // Enter で投稿する設定 + context.Settings.Local.StatusText = "foo"; + context.Settings.Local.StatusMultiline = false; // 単一行モード + + // Shift キーが押されている場合はフッターを無効化する + Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift)); + }); + } + + [WinFormsFact] + public void FormatStatusText_DisableFooterIfPostByEnterAndPressedCtrlKeyAndMultilineMode_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = false; + context.Settings.Common.PostShiftEnter = false; // Enter で投稿する設定 + context.Settings.Local.StatusText = "foo"; + context.Settings.Local.StatusMultiline = true; // 複数行モード + + // Ctrl キーが押されている場合はフッターを無効化する + Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control)); + }); + } + + [WinFormsFact] + public void FormatStatusText_DisableFooterIfPostByShiftEnterAndPressedControlKey_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = false; + context.Settings.Common.PostShiftEnter = true; // Shift+Enter で投稿する設定 + context.Settings.Local.StatusText = "foo"; + + // Ctrl キーが押されている場合はフッターを無効化する + Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control)); + }); + } + + [WinFormsFact] + public void FormatStatusText_EnableFooterIfPostByShiftEnter_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = false; + context.Settings.Common.PostShiftEnter = true; // Shift+Enter で投稿する設定 + context.Settings.Local.StatusText = "foo"; + + // Shift+Enter で投稿する場合、Ctrl キーが押されていなければフッターを付ける + Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift)); + }); + } + + [WinFormsFact] + public void FormatStatusText_DisableFooterIfPostByCtrlEnterAndPressedShiftKey_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = true; // Ctrl+Enter で投稿する設定 + context.Settings.Common.PostShiftEnter = false; + context.Settings.Local.StatusText = "foo"; + + // Shift キーが押されている場合はフッターを無効化する + Assert.Equal("aaa", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Shift)); + }); + } + + [WinFormsFact] + public void FormatStatusText_EnableFooterIfPostByCtrlEnter_Test() + { + this.UsingTweenMain((tweenMain, context) => + { + context.Settings.Common.PostCtrlEnter = true; // Ctrl+Enter で投稿する設定 + context.Settings.Common.PostShiftEnter = false; + context.Settings.Local.StatusText = "foo"; + + // Ctrl+Enter で投稿する場合、Shift キーが押されていなければフッターを付ける + Assert.Equal("aaa foo", tweenMain.FormatStatusText("aaa", modifierKeys: Keys.Control)); + }); + } + [WinFormsFact] public void FormatStatusText_PreventSmsCommand_Test() { diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 00cef7aa5..ae3c66027 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -3530,10 +3530,13 @@ private string FormatStatusTextExtended(string statusText, out long[] autoPopula return this.FormatStatusText(statusText); } + internal string FormatStatusText(string statusText) + => this.FormatStatusText(statusText, Control.ModifierKeys); + /// /// ツイート投稿前のフッター付与などの前処理を行います /// - internal string FormatStatusText(string statusText) + internal string FormatStatusText(string statusText, Keys modifierKeys) { statusText = statusText.Replace("\r\n", "\n"); @@ -3556,14 +3559,14 @@ internal string FormatStatusText(string statusText) bool disableFooter; if (this.settings.Common.PostShiftEnter) { - disableFooter = MyCommon.IsKeyDown(Keys.Control); + disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Control); } else { - if (this.StatusText.Multiline && !this.settings.Common.PostCtrlEnter) - disableFooter = MyCommon.IsKeyDown(Keys.Control); + if (this.settings.Local.StatusMultiline && !this.settings.Common.PostCtrlEnter) + disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Control); else - disableFooter = MyCommon.IsKeyDown(Keys.Shift); + disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Shift); } if (statusText.Contains("RT @")) From 01401067045478496867dbcfb73f2c5400349d1a Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 16 Dec 2023 01:02:10 +0900 Subject: [PATCH 75/76] =?UTF-8?q?ChangeLog=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/opentween/OpenTween/pull/273 による変更のうち HandleTimeout メソッドを追加したことで、API リクエストがタイムアウトした場合に キャンセル処理が完全に終了するのを待たずに Task が完了するようになる。 TimelineScheduler による定期的なタイムライン更新では、非同期タスクの完了を待って から次の更新をスケジュールする挙動のため、更新処理がタイムアウト時間を大幅に 超えても完了しない状態になると次回以降の更新が行われなくなってしまう。 https://github.com/opentween/OpenTween/issues/259 のエラーが起こる原因は 特定できていないが、もし HttpClient のキャンセル処理が原因であればこの変更で 改善される可能性がある。 --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 826fc3c39..c91512c1a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,7 @@ ==== Unreleased * NEW: graphqlエンドポイント経由で取得した引用ツイートの表示に対応 + * FIX: APIリクエストがタイムアウトした場合のキャンセル処理を改善 ==== Ver 3.9.0(2023/12/03) * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 From e4a828feb7d9671bc51b994acd12cda72449fc57 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 16 Dec 2023 01:39:20 +0900 Subject: [PATCH 76/76] =?UTF-8?q?OpenTween=20v3.10.0=20=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 2 +- OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c91512c1a..51ceeb19e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,6 @@ 更新履歴 -==== Unreleased +==== Ver 3.10.0(2023/12/16) * NEW: graphqlエンドポイント経由で取得した引用ツイートの表示に対応 * FIX: APIリクエストがタイムアウトした場合のキャンセル処理を改善 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index b81f8665a..9b3383696 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.9.0.1")] +[assembly: AssemblyVersion("3.10.0.0")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index b90eb7404..aebbd9b49 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -580,7 +580,11 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// - ///==== Unreleased + ///==== Ver 3.10.0(2023/12/16) + /// * NEW: graphqlエンドポイント経由で取得した引用ツイートの表示に対応 + /// * FIX: APIリクエストがタイムアウトした場合のキャンセル処理を改善 + /// + ///==== Ver 3.9.0(2023/12/03) /// * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 /// * CHG: タイムライン更新時に全件ではなく新着投稿のみ差分を取得する動作に変更 /// * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不具合を修正 @@ -590,10 +594,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { ///==== Ver 3.8.0(2023/11/29) /// * NEW: graphqlエンドポイントを使用した検索タイムラインの取得に対応 /// * NEW: graphqlエンドポイントを使用したプロフィール情報の取得に対応 - /// * NEW: graphqlエンドポイントを使用したユーザータイムラインの取得に対応 - /// * CHG: タイムライン更新が停止する不具合が報告される件への暫定的な対処 - /// - タイムライン更新に30秒以上掛かっている場合は完了を待機せず次のタイマーを開始させる - /// - [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// * NEW: graphq [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get {