diff --git a/.editorconfig b/.editorconfig index 35fd69e1b..b6a054ebf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -38,13 +38,13 @@ csharp_new_line_before_open_brace=all csharp_new_line_between_query_expression_clauses=true # http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_prefer_braces -csharp_prefer_braces = true:error +csharp_prefer_braces=true:error # http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_prefer_simple_default_expression csharp_prefer_simple_default_expression=false:error # IDE0063: Use simple 'using' statement -csharp_prefer_simple_using_statement = false +csharp_prefer_simple_using_statement=false # http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_preserve_single_line_blocks csharp_preserve_single_line_blocks=true @@ -131,7 +131,7 @@ csharp_style_expression_bodied_constructors=false:warning csharp_style_expression_bodied_indexers=true:error # http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_methods -csharp_style_expression_bodied_methods=true +csharp_style_expression_bodied_methods=false:warning # http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_operators csharp_style_expression_bodied_operators=false:suggestion @@ -263,4 +263,4 @@ dotnet_naming_style.pascal_case_style.capitalization=pascal_case resharper_arrange_this_qualifier_highlighting=hint # IDE1006: Naming Styles -dotnet_diagnostic.IDE1006.severity = warning \ No newline at end of file +dotnet_diagnostic.ide1006.severity=warning diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e1fb21eaa..36987788d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,6 +39,9 @@ build-release: - echo "Restoring NuGet Packages..." - 'c:\nuget\nuget.exe restore ProtonVpn.sln' - MSBuild.exe /consoleloggerparameters:ErrorsOnly /maxcpucount /nologo /property:Configuration=Release /verbosity:quiet "src\\ProtonVPN.MarkupValidator\\ProtonVPN.MarkupValidator.csproj" + - echo "Building native dependencies..." + - 'c:\nuget\nuget.exe restore ProtonVPN.InstallActions.sln' + - cmd.exe /c BuildDependencies.bat - python ci\main.py lint-languages - python ci\main.py add-commit-hash $env:CI_COMMIT_SHORT_SHA - python ci\main.py defaultConfig @@ -62,6 +65,9 @@ build-debug: - echo "Restoring NuGet Packages..." - 'c:\nuget\nuget.exe restore ProtonVpn.sln' - MSBuild.exe /consoleloggerparameters:ErrorsOnly /maxcpucount /nologo /property:Configuration=Release /verbosity:quiet "src\\ProtonVPN.MarkupValidator\\ProtonVPN.MarkupValidator.csproj" + - echo "Building native dependencies..." + - 'c:\nuget\nuget.exe restore ProtonVPN.InstallActions.sln' + - cmd.exe /c BuildDependencies.bat - echo "Downloading translations from crowdin..." - python ci\main.py lint-languages - python ci\main.py add-commit-hash $env:CI_COMMIT_SHORT_SHA @@ -86,7 +92,7 @@ tests: - echo "Running tests..." - 'c:\nuget\nuget.exe restore ProtonVpn.sln' - MSBuild.exe /consoleloggerparameters:ErrorsOnly /maxcpucount /nologo /property:Configuration=Debug /verbosity:quiet "ProtonVpn.sln" - - coverlet src\bin --target "vstest.console.exe" --targetargs "src/bin/ProtonVPN*Test*.dll /TestCaseFilter:TestCategory!=UI&TestCategory!=Connection /Platform:x64" --format cobertura --output .\coverage-reports --exclude "[*.Test*]*" --exclude "[ProtonVPN.MarkupValidator]*" --exclude "[TestTools*]*" --exclude "[TapInstaller]*" --exclude "[*.Installers]*" + - coverlet src\bin --target "vstest.console.exe" --targetargs "src/bin/ProtonVPN*Tests*.dll /TestCaseFilter:TestCategory!=UI&TestCategory!=Connection /Platform:x64" --format cobertura --output .\coverage-reports --exclude "[*.Tests*]*" --exclude "[ProtonVPN.MarkupValidator]*" --exclude "[TestTools*]*" --exclude "[TapInstaller]*" --exclude "[*.Installers]*" - powershell -Command "(gc coverage-reports.cobertura.xml) -replace '\\', '/' | Out-File -encoding UTF8 cobertura.xml" - ReportGenerator.exe "-reports:cobertura.xml" "-targetdir:.\code-coverage-report-html" artifacts: @@ -150,7 +156,7 @@ ui-test: script: - powershell -File ci\uninstall-app.ps1 - powershell -File ci\install-the-app.ps1 - - VSTest.Console.exe src\bin\ProtonVPN.UI.Test.dll /TestCaseFilter:"Category=UI" + - VSTest.Console.exe src\bin\ProtonVPN.UI.Tests.dll /TestCaseFilter:"Category=UI" - powershell -File ci\uninstall-app.ps1 except: - /^debug.*$/ @@ -169,7 +175,27 @@ connection-tests: script: - powershell -File ci\uninstall-app.ps1 - powershell -File ci\install-the-app.ps1 - - VSTest.Console.exe src\bin\ProtonVPN.UI.Test.dll /TestCaseFilter:"Category=Connection" + - VSTest.Console.exe src\bin\ProtonVPN.UI.Tests.dll /TestCaseFilter:"Category=Connection" + - powershell -File ci\uninstall-app.ps1 + except: + - /^debug.*$/ + - release/9.9.9 + - master + artifacts: + when: on_failure + paths: + - $SCREENSHOT_PATH + expire_in: 2 weeks + +win-11-tests: + stage: ui-test + when: manual + tags: + - win11 + script: + - powershell -File ci\uninstall-app.ps1 + - powershell -File ci\install-the-app.ps1 + - VSTest.Console.exe src\bin\ProtonVPN.UI.Tests.dll /TestCaseFilter:"Category=Smoke" - powershell -File ci\uninstall-app.ps1 except: - /^debug.*$/ diff --git a/BuildDependencies.bat b/BuildDependencies.bat new file mode 100644 index 000000000..73fb9e87f --- /dev/null +++ b/BuildDependencies.bat @@ -0,0 +1,45 @@ +set outputDirX86=..\bin\x86\ +set outputDirX64=..\bin\x64\ +set platformToolset=v143 + +::ProtonVPN.IpFilter.dll (x86) +msbuild src\ProtonVPN.IpFilterLib\ProtonVPN.IpFilterLib.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=Win32 /p:Configuration=Release /p:OutDir=%outputDirX86% +msbuild src\ProtonVPN.IpFilter\ProtonVPN.IpFilter.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=Win32 /p:Configuration=Release /p:OutDir=%outputDirX86% + +::ProtonVPN.IpFilter.dll (x64) +msbuild src\ProtonVPN.IpFilterLib\ProtonVPN.IpFilterLib.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=x64 /p:Configuration=Release /p:OutDir=%outputDirX64% +msbuild src\ProtonVPN.IpFilter\ProtonVPN.IpFilter.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=x64 /p:Configuration=Release /p:OutDir=%outputDirX64% + +::ProtonVPN.NetworkUtil.dll (x86) +msbuild src\ProtonVPN.NetworkUtil\ProtonVPN.NetworkUtil.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=Win32 /p:Configuration=Release /p:OutDir=%outputDirX86% + +::ProtonVPN.NetworkUtil.dll (x64) +msbuild src\ProtonVPN.NetworkUtil\ProtonVPN.NetworkUtil.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=x64 /p:Configuration=Release /p:OutDir=%outputDirX64% + +::ProtonVPN.InstallActions.dll (x86) +msbuild src\ProtonVPN.InstallActions\ProtonVPN.InstallActions.vcxproj /p:PlatformToolset=%platformToolset% /p:Platform=Win32 /p:Configuration=Release + +::GoSrp.dll +pushd %~dp0\src\srp\windows\cshared +set fn=GoSrp +set gn=main.go +set CGO_ENABLED=1 + +set GOARCH=386 +go build -buildmode=c-shared -v -ldflags="-s -w" -o ..\..\..\bin\x86\%fn%.dll %gn% + +set GOARCH=amd64 +go build -buildmode=c-shared -v -ldflags="-s -w" -o ..\..\..\bin\x64\%fn%.dll %gn% + +::LocalAgent.dll +pushd %~dp0\src\ProtonVPN.LocalAgent\localAgentWin +set GOOS=windows +set GO111MODULE=off +set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601 +set CC=x86_64-w64-mingw32-gcc + +set GOARCH=386 +go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "..\..\bin\Resources\32-bit\LocalAgent.dll" || exit /b 1 + +set GOARCH=amd64 +go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "..\..\bin\Resources\64-bit\LocalAgent.dll" || exit /b 1 \ No newline at end of file diff --git a/ProtonVPN.InstallActions.sln b/ProtonVPN.InstallActions.sln new file mode 100644 index 000000000..1ec3e12b8 --- /dev/null +++ b/ProtonVPN.InstallActions.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.InstallActions", "src\ProtonVPN.InstallActions\ProtonVPN.InstallActions.vcxproj", "{BBCBB464-BDCB-49CD-830F-965D07CE1446}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|x86.ActiveCfg = Debug|Win32 + {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|x86.Build.0 = Debug|Win32 + {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|x86.ActiveCfg = Release|Win32 + {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {41A36481-10DA-4E55-9DD7-15950838C9CD} + EndGlobalSection +EndGlobal diff --git a/ProtonVPN.NetworkDrivers.sln b/ProtonVPN.NetworkDrivers.sln new file mode 100644 index 000000000..1439e6f65 --- /dev/null +++ b/ProtonVPN.NetworkDrivers.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.CalloutDriver", "src\ProtonVPN.CalloutDriver\ProtonVPN.CalloutDriver.vcxproj", "{85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x64.ActiveCfg = Debug|x64 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x64.Build.0 = Debug|x64 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x86.ActiveCfg = Debug|Win32 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x86.Build.0 = Debug|Win32 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x64.ActiveCfg = Release|x64 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x64.Build.0 = Release|x64 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x86.ActiveCfg = Release|Win32 + {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D4840175-D6ED-49DB-BD16-67DF919A17B4} + EndGlobalSection +EndGlobal diff --git a/ProtonVPN.Networking.sln b/ProtonVPN.Networking.sln new file mode 100644 index 000000000..8fcdfb230 --- /dev/null +++ b/ProtonVPN.Networking.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.IPFilter", "src\ProtonVPN.IPFilter\ProtonVPN.IpFilter.vcxproj", "{75126704-A03B-4AA2-B182-189263397E8F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.IpFilterLib", "src\ProtonVPN.IpFilterLib\ProtonVPN.IpFilterLib.vcxproj", "{22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.NetworkUtil", "src\ProtonVPN.NetworkUtil\ProtonVPN.NetworkUtil.vcxproj", "{912374C4-7A74-4313-8956-1B32383918C8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x64.ActiveCfg = Debug|x64 + {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x64.Build.0 = Debug|x64 + {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x86.ActiveCfg = Debug|Win32 + {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x86.Build.0 = Debug|Win32 + {75126704-A03B-4AA2-B182-189263397E8F}.Release|x64.ActiveCfg = Release|x64 + {75126704-A03B-4AA2-B182-189263397E8F}.Release|x64.Build.0 = Release|x64 + {75126704-A03B-4AA2-B182-189263397E8F}.Release|x86.ActiveCfg = Release|Win32 + {75126704-A03B-4AA2-B182-189263397E8F}.Release|x86.Build.0 = Release|Win32 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x64.ActiveCfg = Debug|x64 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x64.Build.0 = Debug|x64 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x86.ActiveCfg = Debug|Win32 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x86.Build.0 = Debug|Win32 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x64.ActiveCfg = Release|x64 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x64.Build.0 = Release|x64 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x86.ActiveCfg = Release|Win32 + {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x86.Build.0 = Release|Win32 + {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x64.ActiveCfg = Debug|x64 + {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x64.Build.0 = Debug|x64 + {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x86.ActiveCfg = Debug|Win32 + {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x86.Build.0 = Debug|Win32 + {912374C4-7A74-4313-8956-1B32383918C8}.Release|x64.ActiveCfg = Release|x64 + {912374C4-7A74-4313-8956-1B32383918C8}.Release|x64.Build.0 = Release|x64 + {912374C4-7A74-4313-8956-1B32383918C8}.Release|x86.ActiveCfg = Release|Win32 + {912374C4-7A74-4313-8956-1B32383918C8}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0E2C8B72-A8BC-4E78-95A9-FF72D0A876C3} + EndGlobalSection +EndGlobal diff --git a/ProtonVpn.sln b/ProtonVpn.sln index db21947bf..22c45a410 100644 --- a/ProtonVpn.sln +++ b/ProtonVpn.sln @@ -1,13 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.0.31423.177 +VisualStudioVersion = 17.2.32630.192 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service", "src\ProtonVPN.Service\ProtonVPN.Service.csproj", "{25781B52-5858-4387-80A5-C9C38C32B3CC}" ProjectSection(ProjectDependencies) = postProject - {75126704-A03B-4AA2-B182-189263397E8F} = {75126704-A03B-4AA2-B182-189263397E8F} {CA44B51D-7645-413A-818F-2C5B57DB67DD} = {CA44B51D-7645-413A-818F-2C5B57DB67DD} - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D} = {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D} {96C5D688-C0F1-4A63-9E26-E485FD0E1365} = {96C5D688-C0F1-4A63-9E26-E485FD0E1365} EndProjectSection EndProject @@ -23,11 +21,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service.Contract" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Core", "src\ProtonVPN.Core\ProtonVPN.Core.csproj", "{CA44B51D-7645-413A-818F-2C5B57DB67DD}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{A592E157-F76B-49CE-A5A4-EF130CDD757F}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A592E157-F76B-49CE-A5A4-EF130CDD757F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Core.Test", "test\ProtonVPN.Core.Test\ProtonVPN.Core.Test.csproj", "{FA0D86B4-2B86-4DFE-B7E6-7C809DB74A13}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Core.Tests", "src\Tests\ProtonVPN.Core.Tests\ProtonVPN.Core.Tests.csproj", "{FA0D86B4-2B86-4DFE-B7E6-7C809DB74A13}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.App.Test", "test\ProtonVPN.App.Test\ProtonVPN.App.Test.csproj", "{7F48FD5C-A4C6-496A-B68E-265237C22330}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.App.Tests", "src\Tests\ProtonVPN.App.Tests\ProtonVPN.App.Tests.csproj", "{7F48FD5C-A4C6-496A-B68E-265237C22330}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.TapInstaller", "src\ProtonVPN.TapInstaller\ProtonVPN.TapInstaller.csproj", "{5F6696BC-A37D-4726-82AE-95EF42C307FC}" EndProject @@ -35,69 +33,54 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Vpn", "src\Proton EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Update", "src\ProtonVPN.Update\ProtonVPN.Update.csproj", "{90FDF2B3-25C9-428D-B264-5A5FAEB2D988}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Update.Test", "test\ProtonVPN.Update.Test\ProtonVPN.Update.Test.csproj", "{7D658974-C38C-421F-8186-B735F06CFC58}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Update.Tests", "src\Tests\ProtonVPN.Update.Tests\ProtonVPN.Update.Tests.csproj", "{7D658974-C38C-421F-8186-B735F06CFC58}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Common", "src\ProtonVPN.Common\ProtonVPN.Common.csproj", "{03B8E43C-5680-4803-A745-0A104FE6620C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Resource", "src\ProtonVPN.Resources\ProtonVPN.Resource.csproj", "{45A0EA81-D37E-4D7F-8CE1-CE6B6A95A9ED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Common.Test", "test\ProtonVPN.Common.Test\ProtonVPN.Common.Test.csproj", "{5F2931B6-9A77-4F94-80CD-BC9B9A0C64BF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Common.Tests", "src\Tests\ProtonVPN.Common.Tests\ProtonVPN.Common.Tests.csproj", "{5F2931B6-9A77-4F94-80CD-BC9B9A0C64BF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DAD0837D-1F65-47C7-8FA6-D6D34D979418}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig BUILD.md = BUILD.md + BuildDependencies.bat = BuildDependencies.bat CONTRIBUTING.md = CONTRIBUTING.md COPYING.md = COPYING.md LICENSE = LICENSE README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.UI.Test", "test\ProtonVPN.UI.Test\ProtonVPN.UI.Test.csproj", "{24E940FF-C9F3-4D5C-8FCF-CA527F055318}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.UI.Tests", "src\Tests\ProtonVPN.UI.Tests\ProtonVPN.UI.Tests.csproj", "{24E940FF-C9F3-4D5C-8FCF-CA527F055318}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service.Test", "test\ProtonVPN.Service.Test\ProtonVPN.Service.Test.csproj", "{4290C007-2142-4AD1-8EB6-F80EF2F45AA4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service.Tests", "src\Tests\ProtonVPN.Service.Tests\ProtonVPN.Service.Tests.csproj", "{4290C007-2142-4AD1-8EB6-F80EF2F45AA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Vpn.Test", "test\ProtonVPN.Vpn.Test\ProtonVPN.Vpn.Test.csproj", "{A16637C2-2D91-4953-AE04-D91EC188DD7B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Vpn.Tests", "src\Tests\ProtonVPN.Vpn.Tests\ProtonVPN.Vpn.Tests.csproj", "{A16637C2-2D91-4953-AE04-D91EC188DD7B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Test.Common", "test\ProtonVPN.Test.Common\ProtonVPN.Test.Common.csproj", "{A0DA4200-6643-4F2C-8450-65B8CE8A5576}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Tests.Common", "src\Tests\ProtonVPN.Tests.Common\ProtonVPN.Tests.Common.csproj", "{A0DA4200-6643-4F2C-8450-65B8CE8A5576}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service.Contract.Test", "test\ProtonVPN.Service.Contract.Test\ProtonVPN.Service.Contract.Test.csproj", "{C4E97B08-345A-423B-BD4C-76593D1401B6}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.NetworkUtil", "src\ProtonVPN.NetworkUtil\ProtonVPN.NetworkUtil.vcxproj", "{912374C4-7A74-4313-8956-1B32383918C8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Service.Contract.Tests", "src\Tests\ProtonVPN.Service.Contract.Tests\ProtonVPN.Service.Contract.Tests.csproj", "{C4E97B08-345A-423B-BD4C-76593D1401B6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Native", "src\ProtonVPN.Native\ProtonVPN.Native.csproj", "{CB301B4C-D518-41F5-873B-9B1F145DB4AF}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.InstallActions", "src\ProtonVPN.InstallActions\ProtonVPN.InstallActions.vcxproj", "{BBCBB464-BDCB-49CD-830F-965D07CE1446}" - ProjectSection(ProjectDependencies) = postProject - {25781B52-5858-4387-80A5-C9C38C32B3CC} = {25781B52-5858-4387-80A5-C9C38C32B3CC} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.CalloutDriver", "src\ProtonVPN.CalloutDriver\ProtonVPN.CalloutDriver.vcxproj", "{85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.NetworkFilter", "src\ProtonVPN.NetworkFilter\ProtonVPN.NetworkFilter.csproj", "{1CF1B8BF-57EB-4E49-B644-0A8F2DFEEB58}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.MarkupValidator", "src\ProtonVPN.MarkupValidator\ProtonVPN.MarkupValidator.csproj", "{BA03F069-6A26-45D1-8B05-6E61E3D9D1E1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.TlsVerify", "src\ProtonVPN.TlsVerify\ProtonVPN.TlsVerify.csproj", "{643CA26B-B80E-4E6A-AAE4-BCEC021D2AA0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTools.ProfileCleaner", "test\TestTools.ProfileCleaner\TestTools.ProfileCleaner.csproj", "{4237AD33-596A-4B35-826A-B4E71461820F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTools.ProfileCleaner", "src\Tests\TestTools.ProfileCleaner\TestTools.ProfileCleaner.csproj", "{4237AD33-596A-4B35-826A-B4E71461820F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTools.ApiClient", "test\TestTools.ApiClient\TestTools.ApiClient.csproj", "{F059E362-20A2-472B-82CA-E727D31AC0C7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTools.ApiClient", "src\Tests\TestTools.ApiClient\TestTools.ApiClient.csproj", "{F059E362-20A2-472B-82CA-E727D31AC0C7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Translations", "src\ProtonVPN.Translations\ProtonVPN.Translations.csproj", "{2A00C747-8BC0-4EF1-A53E-37A7E156D910}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.IpFilterLib", "src\ProtonVPN.IpFilterLib\ProtonVPN.IpFilterLib.vcxproj", "{22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtonVPN.IPFilter", "src\ProtonVPN.IPFilter\ProtonVPN.IPFilter.vcxproj", "{75126704-A03B-4AA2-B182-189263397E8F}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.RestoreInternet", "ProtonVPN.RestoreInternet\ProtonVPN.RestoreInternet.csproj", "{8D07769D-0AAE-4224-AECB-DF68567F6B00}" - ProjectSection(ProjectDependencies) = postProject - {BBCBB464-BDCB-49CD-830F-965D07CE1446} = {BBCBB464-BDCB-49CD-830F-965D07CE1446} - EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Crypto", "src\ProtonVPN.Crypto\ProtonVPN.Crypto.csproj", "{BA2D505E-CED3-4FCB-A463-DAF6B77C18DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Crypto.Test", "test\ProtonVPN.Crypto.Test\ProtonVPN.Crypto.Test.csproj", "{7D608265-3330-4747-B5B4-9673A119FE6C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Crypto.Tests", "src\Tests\ProtonVPN.Crypto.Tests\ProtonVPN.Crypto.Tests.csproj", "{7D608265-3330-4747-B5B4-9673A119FE6C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.WireGuardService", "src\ProtonVPN.WireguardService\ProtonVPN.WireGuardService.csproj", "{A0A9C7B9-4A33-492E-BA54-8E8E600D3D66}" EndProject @@ -123,10 +106,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Api.Tests", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.HumanVerification.Gui", "src\HumanVerification\ProtonVPN.HumanVerification.Gui\ProtonVPN.HumanVerification.Gui.csproj", "{7318548D-B8F2-4ED3-8B3E-F61DD8B552D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.IntegrationTests", "test\ProtonVPN.IntegrationTests\ProtonVPN.IntegrationTests.csproj", "{A2B4BEC3-7430-489E-BE6B-F1E8205A7C62}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.IntegrationTests", "src\Tests\ProtonVPN.IntegrationTests\ProtonVPN.IntegrationTests.csproj", "{A2B4BEC3-7430-489E-BE6B-F1E8205A7C62}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtonVPN.SourceGenerators", "src\ProtonVPN.SourceGenerators\ProtonVPN.SourceGenerators.csproj", "{B7B19A7B-5A20-4690-81F4-83002CA064FB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dns", "Dns", "{7D999E1B-C00B-4BA8-AEC3-94BA92010E38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Dns", "src\Dns\ProtonVPN.Dns\ProtonVPN.Dns.csproj", "{8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Dns.Contracts", "src\Dns\ProtonVPN.Dns.Contracts\ProtonVPN.Dns.Contracts.csproj", "{455DA1FB-5097-47D2-8603-B0E1F9D90294}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Dns.Installers", "src\Dns\ProtonVPN.Dns.Installers\ProtonVPN.Dns.Installers.csproj", "{E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonVPN.Dns.Tests", "src\Dns\ProtonVPN.Dns.Tests\ProtonVPN.Dns.Tests.csproj", "{F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -351,12 +344,6 @@ Global {C4E97B08-345A-423B-BD4C-76593D1401B6}.Release|x64.Build.0 = Release|Any CPU {C4E97B08-345A-423B-BD4C-76593D1401B6}.Release|x86.ActiveCfg = Release|Any CPU {C4E97B08-345A-423B-BD4C-76593D1401B6}.Release|x86.Build.0 = Release|Any CPU - {912374C4-7A74-4313-8956-1B32383918C8}.Debug|Any CPU.ActiveCfg = Debug|x64 - {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x64.ActiveCfg = Debug|x64 - {912374C4-7A74-4313-8956-1B32383918C8}.Debug|x86.ActiveCfg = Debug|Win32 - {912374C4-7A74-4313-8956-1B32383918C8}.Release|Any CPU.ActiveCfg = Release|x64 - {912374C4-7A74-4313-8956-1B32383918C8}.Release|x64.ActiveCfg = Release|x64 - {912374C4-7A74-4313-8956-1B32383918C8}.Release|x86.ActiveCfg = Release|Win32 {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -369,22 +356,6 @@ Global {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Release|x64.Build.0 = Release|Any CPU {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Release|x86.ActiveCfg = Release|Any CPU {CB301B4C-D518-41F5-873B-9B1F145DB4AF}.Release|x86.Build.0 = Release|Any CPU - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|Any CPU.Build.0 = Debug|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|x64.ActiveCfg = Debug|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|x86.ActiveCfg = Debug|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Debug|x86.Build.0 = Debug|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|Any CPU.ActiveCfg = Release|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|Any CPU.Build.0 = Release|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|x64.ActiveCfg = Release|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|x86.ActiveCfg = Release|Win32 - {BBCBB464-BDCB-49CD-830F-965D07CE1446}.Release|x86.Build.0 = Release|Win32 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|Any CPU.ActiveCfg = Debug|x64 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x64.ActiveCfg = Debug|x64 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Debug|x86.ActiveCfg = Debug|Win32 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|Any CPU.ActiveCfg = Release|x64 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x64.ActiveCfg = Release|x64 - {85E3F2D0-84AA-4DD2-B23A-B41D779A5FA9}.Release|x86.ActiveCfg = Release|Win32 {1CF1B8BF-57EB-4E49-B644-0A8F2DFEEB58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CF1B8BF-57EB-4E49-B644-0A8F2DFEEB58}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CF1B8BF-57EB-4E49-B644-0A8F2DFEEB58}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -457,18 +428,6 @@ Global {2A00C747-8BC0-4EF1-A53E-37A7E156D910}.Release|x64.Build.0 = Release|Any CPU {2A00C747-8BC0-4EF1-A53E-37A7E156D910}.Release|x86.ActiveCfg = Release|Any CPU {2A00C747-8BC0-4EF1-A53E-37A7E156D910}.Release|x86.Build.0 = Release|Any CPU - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x64.ActiveCfg = Debug|x64 - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Debug|x86.ActiveCfg = Debug|Win32 - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|Any CPU.ActiveCfg = Release|Win32 - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x64.ActiveCfg = Release|x64 - {22381B63-9BA0-4DF8-BDB1-EC4AE1B7470D}.Release|x86.ActiveCfg = Release|Win32 - {75126704-A03B-4AA2-B182-189263397E8F}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x64.ActiveCfg = Debug|x64 - {75126704-A03B-4AA2-B182-189263397E8F}.Debug|x86.ActiveCfg = Debug|Win32 - {75126704-A03B-4AA2-B182-189263397E8F}.Release|Any CPU.ActiveCfg = Release|x64 - {75126704-A03B-4AA2-B182-189263397E8F}.Release|x64.ActiveCfg = Release|x64 - {75126704-A03B-4AA2-B182-189263397E8F}.Release|x86.ActiveCfg = Release|Win32 {8D07769D-0AAE-4224-AECB-DF68567F6B00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D07769D-0AAE-4224-AECB-DF68567F6B00}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D07769D-0AAE-4224-AECB-DF68567F6B00}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -649,6 +608,54 @@ Global {B7B19A7B-5A20-4690-81F4-83002CA064FB}.Release|x64.Build.0 = Release|Any CPU {B7B19A7B-5A20-4690-81F4-83002CA064FB}.Release|x86.ActiveCfg = Release|Any CPU {B7B19A7B-5A20-4690-81F4-83002CA064FB}.Release|x86.Build.0 = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|x64.Build.0 = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Debug|x86.Build.0 = Debug|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|Any CPU.Build.0 = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|x64.ActiveCfg = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|x64.Build.0 = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|x86.ActiveCfg = Release|Any CPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75}.Release|x86.Build.0 = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|Any CPU.Build.0 = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|x64.ActiveCfg = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|x64.Build.0 = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|x86.ActiveCfg = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Debug|x86.Build.0 = Debug|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|Any CPU.ActiveCfg = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|Any CPU.Build.0 = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|x64.ActiveCfg = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|x64.Build.0 = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|x86.ActiveCfg = Release|Any CPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294}.Release|x86.Build.0 = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|x64.ActiveCfg = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|x64.Build.0 = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|x86.ActiveCfg = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Debug|x86.Build.0 = Debug|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|Any CPU.Build.0 = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|x64.ActiveCfg = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|x64.Build.0 = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|x86.ActiveCfg = Release|Any CPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F}.Release|x86.Build.0 = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|x64.ActiveCfg = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|x64.Build.0 = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|x86.ActiveCfg = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Debug|x86.Build.0 = Debug|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|Any CPU.Build.0 = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|x64.ActiveCfg = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|x64.Build.0 = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|x86.ActiveCfg = Release|Any CPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -675,6 +682,10 @@ Global {168C5EC3-FF08-402F-8D70-F216CB04DE81} = {DCDB1771-1943-4459-8040-F001758E6E1E} {7318548D-B8F2-4ED3-8B3E-F61DD8B552D1} = {95E8A2C3-A9CF-4911-83AB-74498B7D1528} {A2B4BEC3-7430-489E-BE6B-F1E8205A7C62} = {A592E157-F76B-49CE-A5A4-EF130CDD757F} + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75} = {7D999E1B-C00B-4BA8-AEC3-94BA92010E38} + {455DA1FB-5097-47D2-8603-B0E1F9D90294} = {7D999E1B-C00B-4BA8-AEC3-94BA92010E38} + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F} = {7D999E1B-C00B-4BA8-AEC3-94BA92010E38} + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF} = {7D999E1B-C00B-4BA8-AEC3-94BA92010E38} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5CFD3EA5-ADCA-48F0-9E3F-BBC76CCBF1C2} diff --git a/Setup/ProtonVPN.aip b/Setup/ProtonVPN.aip index df02b52b6..a143ccd78 100644 --- a/Setup/ProtonVPN.aip +++ b/Setup/ProtonVPN.aip @@ -1,7 +1,7 @@ - + - + @@ -11,6 +11,7 @@ + @@ -133,6 +134,9 @@ + + + @@ -329,12 +333,18 @@ + + + + + + @@ -417,11 +427,11 @@ - + - + @@ -759,6 +769,9 @@ + + + @@ -962,8 +975,8 @@ - - + + diff --git a/VisualStudioItemTemplates/README.txt b/VisualStudioItemTemplates/README.txt index 677005fdf..2f00c0d16 100644 --- a/VisualStudioItemTemplates/README.txt +++ b/VisualStudioItemTemplates/README.txt @@ -1,11 +1,11 @@ In the current folder (VisualStudioItemTemplates) you should have a CSharp folder. That folder should be copied to the inside of the folder: -C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ItemTemplates +C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates It should substitute 3 files. -C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\Class\Class.cs -C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\Interface\Interface.cs -C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\WebClass\Class.cs +C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\Class\Class.cs +C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\Interface\Interface.cs +C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\WebClass\Class.cs Now Visual Studio has proper default files. diff --git a/ci/InstallerTestScripts/prepare-installer-tests.ps1 b/ci/InstallerTestScripts/prepare-installer-tests.ps1 index bcba2d706..72e1e2112 100644 --- a/ci/InstallerTestScripts/prepare-installer-tests.ps1 +++ b/ci/InstallerTestScripts/prepare-installer-tests.ps1 @@ -5,7 +5,7 @@ $testStatus = @{} $installerSharedFolderPath = "C:/Shared/Installers/" + $env:CI_PROJECT_ID $installerPath = $env:CI_PROJECT_DIR + "/setup/ProtonVPN-SetupFiles/*.exe" $testScriptsSharedPath = "C:/Shared/Scripts/" -$testScriptsPath = $env:CI_PROJECT_DIR + "/test/ProtonVPN.UI.Test/InstallerScripts/FreshInstall.xml" +$testScriptsPath = $env:CI_PROJECT_DIR + "/src/Tests/ProtonVPN.UI.Tests/InstallerScripts/FreshInstall.xml" $testRunnerScript = $env:CI_PROJECT_DIR + "/ci/InstallerTestScripts/run-installer-tests.ps1" function Log-Message diff --git a/src/Api/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs b/src/Api/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs new file mode 100644 index 000000000..4c2eecae6 --- /dev/null +++ b/src/Api/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; + +namespace ProtonVPN.Api.Contracts.Exceptions +{ + public class AlternativeRoutingException : Exception + { + public AlternativeRoutingException() + { + } + + public AlternativeRoutingException(string message) : base(message) + { + } + + public AlternativeRoutingException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Contracts/ProtonVPN.Api.Contracts.csproj b/src/Api/ProtonVPN.Api.Contracts/ProtonVPN.Api.Contracts.csproj index 4e0482b27..fbffd7951 100644 --- a/src/Api/ProtonVPN.Api.Contracts/ProtonVPN.Api.Contracts.csproj +++ b/src/Api/ProtonVPN.Api.Contracts/ProtonVPN.Api.Contracts.csproj @@ -60,6 +60,7 @@ + diff --git a/src/Api/ProtonVPN.Api.Installers/ApiModule.cs b/src/Api/ProtonVPN.Api.Installers/ApiModule.cs index fb67442fd..d682da293 100644 --- a/src/Api/ProtonVPN.Api.Installers/ApiModule.cs +++ b/src/Api/ProtonVPN.Api.Installers/ApiModule.cs @@ -30,24 +30,35 @@ public class ApiModule : Module { protected override void Load(ContainerBuilder builder) { + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().SingleInstance(); - builder.RegisterType().SingleInstance(); - builder.RegisterType().As().AsSelf().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().SingleInstance(); - builder.RegisterType().As().AsSelf().SingleInstance(); - builder.RegisterType().SingleInstance(); - builder.RegisterType().As().AsSelf().SingleInstance(); - builder.RegisterType().SingleInstance(); - builder.RegisterType().As().AsSelf().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().As().SingleInstance(); + + RegisterHandlers(builder); + } + + // Handlers are currently being used by both ApiClient and TokenClient and we need different instances + // of the handlers for them since the stacks are different, hence the InstancePerDependency() usage + private void RegisterHandlers(ContainerBuilder builder) + { + builder.RegisterType().As().AsSelf().InstancePerDependency(); + builder.RegisterType().As().AsSelf().InstancePerDependency(); + builder.RegisterType().As().AsSelf().InstancePerDependency(); + builder.RegisterType().As().AsSelf().InstancePerDependency(); + + builder.RegisterType().AsImplementedInterfaces().AsSelf().InstancePerDependency(); + + builder.RegisterType().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); } } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/ApiClientTest.cs b/src/Api/ProtonVPN.Api.Tests/ApiClientTest.cs index 0fad27c03..e59d2a360 100644 --- a/src/Api/ProtonVPN.Api.Tests/ApiClientTest.cs +++ b/src/Api/ProtonVPN.Api.Tests/ApiClientTest.cs @@ -26,7 +26,6 @@ using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Servers; using ProtonVPN.Common.Logging; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Settings; using RichardSzalay.MockHttp; @@ -36,9 +35,9 @@ namespace ProtonVPN.Api.Tests public class ApiClientTest { private ILogger _logger; - private ITokenStorage _tokenStorage; + private IAppSettings _appSettings; private IApiAppVersion _appVersion; - private IHttpClientFactory _httpClientFactory; + private IApiHttpClientFactory _apiHttpClientFactory; private IApiClient _apiClient; private readonly MockHttpMessageHandler _fakeHttpMessageHandler = new(); private IAppLanguageCache _appLanguageCache; @@ -53,18 +52,19 @@ public void TestInitialize() _appVersion.Value().Returns(string.Empty); _appVersion.UserAgent().Returns("User agent"); - _tokenStorage = Substitute.For(); - _tokenStorage.AccessToken.Returns(string.Empty); - _tokenStorage.Uid.Returns(string.Empty); + _appSettings = Substitute.For(); + _appSettings.AccessToken.Returns(string.Empty); + _appSettings.Uid.Returns(string.Empty); HttpClient httpClient = _fakeHttpMessageHandler.ToHttpClient(); httpClient.BaseAddress = new("http://127.0.0.1"); - _httpClientFactory = Substitute.For(); - _httpClientFactory.GetApiHttpClientWithoutCache().Returns(httpClient); - _httpClientFactory.GetApiHttpClientWithCache().Returns(httpClient); + _apiHttpClientFactory = Substitute.For(); + _apiHttpClientFactory.GetApiHttpClientWithoutCache().Returns(httpClient); + _apiHttpClientFactory.GetApiHttpClientWithCache().Returns(httpClient); - _apiClient = new ApiClient(_httpClientFactory, _logger, _tokenStorage, _appVersion, _appLanguageCache, new Common.Configuration.Config()); + _apiClient = new ApiClient(_apiHttpClientFactory, _logger, _appSettings, _appVersion, _appLanguageCache, + new Common.Configuration.Config()); } [TestMethod] diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/AlternativeHostHandlerTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/AlternativeHostHandlerTest.cs index 6d6b080fa..49f00afe4 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/AlternativeHostHandlerTest.cs +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/AlternativeHostHandlerTest.cs @@ -21,17 +21,25 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Reflection; +using System.Security.Authentication; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; -using ProtonVPN.Api.Contracts; +using Polly.Timeout; +using ProtonVPN.Api.Contracts.Exceptions; using ProtonVPN.Api.Handlers; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Logging; -using ProtonVPN.Core.Abstract; -using ProtonVPN.Core.OS.Net.DoH; +using ProtonVPN.Common.Networking; +using ProtonVPN.Common.Vpn; using ProtonVPN.Core.Settings; +using ProtonVPN.Core.Vpn; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.AlternativeRouting; +using ProtonVPN.Dns.Contracts.Exceptions; using RichardSzalay.MockHttp; namespace ProtonVPN.Api.Tests.Handlers @@ -39,50 +47,1265 @@ namespace ProtonVPN.Api.Tests.Handlers [TestClass] public class AlternativeHostHandlerTest { - private const string BASE_API_URL = "https://api.protonvpn.ch"; + private const string HTTPS = "https://"; + private const string HOST = "api.protonvpn.ch"; + private const string PATH = "/auth"; + private const string REQUEST_URL = HTTPS + HOST + PATH; - private MockHttpMessageHandler _innerHandler; + private const string CONFIG_API_HOST = "configapi.protonvpn.ch"; + private const string CONFIG_API_BASE_URL = HTTPS + CONFIG_API_HOST + "/"; + private const string CONFIG_API_PING_URL = CONFIG_API_BASE_URL + AlternativeHostHandler.API_PING_TEST_PATH; + + private const string IP_ADDRESS_1 = "192.168.1.1"; + private const string IP_ADDRESS_2 = "192.168.2.2"; + private const string IP_ADDRESS_3 = "192.168.3.3"; + + private const string ALTERNATIVE_URL_1 = HTTPS + IP_ADDRESS_1 + PATH; + private const string ALTERNATIVE_URL_2 = HTTPS + IP_ADDRESS_2 + PATH; + private const string ALTERNATIVE_URL_3 = HTTPS + IP_ADDRESS_3 + PATH; + + private const string ALTERNATIVE_HOST_1 = "abc.protonvpn.com"; + private const string ALTERNATIVE_HOST_2 = "def.protonvpn.com"; + private const string ALTERNATIVE_HOST_3 = "ghi.protonvpn.com"; + + private const string USER_ID = "userId12"; + + private readonly static TimeSpan ALTERNATIVE_ROUTING_CHECK_INTERVAL = TimeSpan.FromMinutes(30); + + private ILogger _logger; + private IDnsManager _dnsManager; + private IAlternativeRoutingHostGenerator _alternativeRoutingHostGenerator; + private IAlternativeHostsManager _alternativeHostsManager; private IAppSettings _appSettings; - private ITokenStorage _tokenStorage; - private IApiHostProvider _apiHostProvider; - private Config _config; - private readonly Uri _baseAddress = new(BASE_API_URL); + private GuestHoleState _guestHoleState; + private IConfiguration _configuration; + private MockHttpMessageHandler _mockHttpMessageHandler; + private AlternativeHostHandler _alternativeHostHandler; + private HttpClient _httpClient; + private int _originalRequestCount; [TestInitialize] public void TestInitialize() { - _innerHandler = new MockHttpMessageHandler(); + _logger = Substitute.For(); + _dnsManager = Substitute.For(); + _alternativeRoutingHostGenerator = Substitute.For(); + _alternativeHostsManager = Substitute.For(); _appSettings = Substitute.For(); - _tokenStorage = Substitute.For(); - _apiHostProvider = Substitute.For(); - _config = new Config { Urls = { ApiUrl = BASE_API_URL } }; + _appSettings.Uid.Returns(USER_ID); + _guestHoleState = new GuestHoleState(); + _configuration = new Config() + { + Urls = { ApiUrl = CONFIG_API_BASE_URL }, + AlternativeRoutingCheckInterval = ALTERNATIVE_ROUTING_CHECK_INTERVAL + }; + _mockHttpMessageHandler = new MockHttpMessageHandler(); + _alternativeHostHandler = new(_logger, _dnsManager, _alternativeRoutingHostGenerator, + _alternativeHostsManager, _appSettings, _guestHoleState, _configuration) + { InnerHandler = _mockHttpMessageHandler }; + _httpClient = new(_alternativeHostHandler); + _originalRequestCount = 0; + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _dnsManager = null; + _alternativeRoutingHostGenerator = null; + _alternativeHostsManager = null; + _appSettings = null; + _guestHoleState = null; + _configuration = null; + _mockHttpMessageHandler = null; + _alternativeHostHandler = null; + _httpClient = null; + _originalRequestCount = 0; + } + + [TestMethod] + public async Task Test_Success() + { + // Arrange + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + await NoDependenciesAreCalledAsync(); + } + + private async Task NoDependenciesAreCalledAsync() + { + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(0).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_WithUnusualException() + { + // Arrange + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw new ArgumentNullException()); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + await NoDependenciesAreCalledAsync(); + } + + [TestMethod] + [DataRow(VpnStatus.Disconnected, true, true)] + [DataRow(VpnStatus.Disconnected, false, false)] + [DataRow(VpnStatus.Pinging, false, true)] + [DataRow(VpnStatus.Connecting, false, true)] + [DataRow(VpnStatus.Reconnecting, false, true)] + [DataRow(VpnStatus.Waiting, false, true)] + [DataRow(VpnStatus.Authenticating, false, true)] + [DataRow(VpnStatus.RetrievingConfiguration, false, true)] + [DataRow(VpnStatus.AssigningIp, false, true)] + [DataRow(VpnStatus.Connected, false, true)] + [DataRow(VpnStatus.Disconnecting, false, true)] + [DataRow(VpnStatus.ActionRequired, false, true)] + public async Task Test_WithBlockingExceptionButAlternativeRoutingNotAllowed( + VpnStatus vpnStatus, bool isGuestHoleActive, bool isDoHEnabled) + { + // Arrange + await SetVpnStatusAsync(vpnStatus); + _guestHoleState.SetState(isGuestHoleActive); + _appSettings.DoHEnabled = isDoHEnabled; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw new TimeoutException()); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + await NoDependenciesAreCalledAsync(); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_NoAlternativeHosts(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + private Exception CreateException(Type exceptionType) + { + return (Exception)Activator.CreateInstance(exceptionType, "Test exception message"); + } + + private async Task SetVpnStatusAsync(VpnStatus status) + { + await _alternativeHostHandler.OnVpnStateChanged(new VpnStateChangedEventArgs(new VpnState(status), VpnError.None, false)); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_AlternativeHostsHaveNoIpAddresses(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + private IList CreateAlternativeHostsList() + { + return new List() { ALTERNATIVE_HOST_1, ALTERNATIVE_HOST_2, ALTERNATIVE_HOST_3 }; + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(ALTERNATIVE_HOST_3, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + private IList CreateIpAddressList() + { + return new List() + { + new(IPAddress.Parse(IP_ADDRESS_1)), + new(IPAddress.Parse(IP_ADDRESS_2)), + new(IPAddress.Parse(IP_ADDRESS_3)), + }; } [TestMethod] - public async Task SendAsync_ShouldNotTriggerAlternativeHostIfItIsDisabled() + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_AllIpAddressesOfAllAlternativeHostsFail(Type exceptionType) { // Arrange - _appSettings.DoHEnabled.Returns(false); - _appSettings.ActiveAlternativeApiBaseUrl = "alternative.api.url"; - _appSettings.LastPrimaryApiFailDateUtc = DateTime.UtcNow; + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError3 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => throw CreateException(exceptionType)); - AlternativeHostHandler handler = new( - new MockOfCancellingHandler(_innerHandler), - Substitute.For(), - new DohClients(new List { "provider1", "provider2" }, TimeSpan.FromSeconds(10)), - new MainHostname(BASE_API_URL), - _appSettings, - new GuestHoleState(), - _tokenStorage, - _apiHostProvider, - _config) { InnerHandler = _innerHandler }; + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); - HttpClient client = new(handler) { BaseAddress = _baseAddress }; + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(3); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(3); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError3).Should().Be(3); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_AllIpAddressesOfAllAlternativeHostsFailWithErrorCode(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => new HttpResponseMessage(HttpStatusCode.NotFound)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => new HttpResponseMessage(HttpStatusCode.NotFound)); + MockedRequest mockedRequestError3 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new HttpResponseMessage(HttpStatusCode.NotFound)); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(3); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(3); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError3).Should().Be(3); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_CacheExists_SucceedsOnSecondAttempt(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => ThrowOnFirstHttpResponseMessageThenSucceed(exceptionType)); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + private HttpResponseMessage ThrowOnFirstHttpResponseMessageThenSucceed(Type exceptionType) + { + _originalRequestCount++; + return _originalRequestCount <= 1 + ? throw CreateException(exceptionType) + : new(HttpStatusCode.OK); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_CacheExists_PingSucceeds_FailsOriginalRequest_AlternativeRoutingFails(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + // Assert + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_CacheExists_PingSucceeds_FailsOriginalRequest_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(ALTERNATIVE_HOST_3, Arg.Any()) + .Returns(CreateIpAddressList()); + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_CacheExists_PingThrows_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + await Test_CacheExists_PingError_LastIpAddressOfLastAlternativeHostWorks( + exceptionType, () => throw CreateException(exceptionType)); + } + + private async Task Test_CacheExists_PingError_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType, + Func configApiPingResponse) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(ALTERNATIVE_HOST_3, Arg.Any()) + .Returns(CreateIpAddressList()); + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => configApiPingResponse()); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_CacheExists_PingFails_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + await Test_CacheExists_PingError_LastIpAddressOfLastAlternativeHostWorks( + exceptionType, () => new(HttpStatusCode.BadRequest)); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task Test_ResolveSucceeds_SucceedsOnSecondAttempt(Type exceptionType) + { + // Arrange + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => ThrowOnFirstHttpResponseMessageThenSucceed(exceptionType)); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckNull_ResolveWorks_ApiIsAvailable_Succeeds() + { + // Arrange + SetActiveAlternativeHost(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + private void SetActiveAlternativeHost() + { + FieldInfo field = typeof(AlternativeHostHandler).GetField("_activeAlternativeHost", + BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(_alternativeHostHandler, ALTERNATIVE_HOST_2); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckNull_CacheWorks_ApiIsAvailable_Succeeds() + { + // Arrange + SetActiveAlternativeHost(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); - _innerHandler.Expect(HttpMethod.Get, _baseAddress.ToString()).Respond(HttpStatusCode.OK); + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_CacheWorks_ApiIsAvailable_Succeeds() + { + // Arrange + SetActiveAlternativeHost(); + SetOldLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetFromCache(CONFIG_API_HOST) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.OK)); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + private void SetOldLastAlternativeRoutingCheckDateUtc() + { + FieldInfo field = typeof(AlternativeHostHandler).GetField("_lastAlternativeRoutingCheckDateUtc", + BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(_alternativeHostHandler, DateTime.UtcNow.AddSeconds(-1) - ALTERNATIVE_ROUTING_CHECK_INTERVAL); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_AlternativeRoutingSucceeds() + { + // Arrange + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Arrange + Act + Assert + await InternalTest_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_AlternativeRoutingSucceeds( + request, mockedRequestError1, mockedRequestError2, mockedRequestSuccess); + } + + private async Task InternalTest_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_AlternativeRoutingSucceeds( + HttpRequestMessage request, params MockedRequest[] mockedRequests) + { + // Arrange + SetActiveAlternativeHost(); + SetOldLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetAsync(ALTERNATIVE_HOST_2, Arg.Any()) + .Returns(CreateIpAddressList()); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + foreach (MockedRequest mockedRequest in mockedRequests) + { + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + } + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_ResolveWorksPingThrows_AlternativeRoutingSucceeds() + { + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => throw new Exception("Test exception")); + await Test_AlternativeRoutingEnabled_LastCheckIsOld_ResolveWorksPingFails_AlternativeRoutingSucceeds( + mockedPingRequest); + } + + private async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_ResolveWorksPingFails_AlternativeRoutingSucceeds(MockedRequest mockedPingRequest) + { + // Arrange + SetActiveAlternativeHost(); + SetOldLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()) + .Returns(CreateIpAddressList()); + _dnsManager.GetAsync(ALTERNATIVE_HOST_2, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedPingRequest).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_ResolveWorksPingReturnsError_AlternativeRoutingSucceeds() + { + MockedRequest mockedPingRequest = _mockHttpMessageHandler + .When(CONFIG_API_PING_URL) + .Respond(_ => new(HttpStatusCode.BadRequest)); + await Test_AlternativeRoutingEnabled_LastCheckIsOld_ResolveWorksPingFails_AlternativeRoutingSucceeds( + mockedPingRequest); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_AlternativeRoutingFails_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + SetOldLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetAsync(ALTERNATIVE_HOST_2, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError3 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError3).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_NoAlternativeRoutingDns_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + SetOldLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsFresh_AlternativeRoutingSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + SetFreshLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetAsync(ALTERNATIVE_HOST_2, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(0).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + private void SetFreshLastAlternativeRoutingCheckDateUtc() + { + FieldInfo field = typeof(AlternativeHostHandler).GetField("_lastAlternativeRoutingCheckDateUtc", + BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(_alternativeHostHandler, DateTime.UtcNow); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsFresh_AlternativeRoutingFails_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + SetFreshLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _dnsManager.GetAsync(ALTERNATIVE_HOST_2, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError3 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError3).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(0).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_LastCheckIsFresh_NoAlternativeRoutingDns_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + SetFreshLastAlternativeRoutingCheckDateUtc(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(0).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(VpnStatus.Pinging)] + [DataRow(VpnStatus.Connecting)] + [DataRow(VpnStatus.Reconnecting)] + [DataRow(VpnStatus.Waiting)] + [DataRow(VpnStatus.Authenticating)] + [DataRow(VpnStatus.RetrievingConfiguration)] + [DataRow(VpnStatus.AssigningIp)] + [DataRow(VpnStatus.Connected)] + [DataRow(VpnStatus.Disconnecting)] + [DataRow(VpnStatus.ActionRequired)] + public async Task Test_AlternativeRoutingEnabled_ButNotAllowedDueToVpnStatus_NormalRequestSucceeds(VpnStatus vpnStatus) + { + // Arrange + SetActiveAlternativeHost(); + await SetVpnStatusAsync(vpnStatus); + _appSettings.DoHEnabled = true; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + await Test_AlternativeRoutingEnabled_ButNotAllowed_NormalRequestSucceeds(request, mockedRequestOriginal); + } + + private async Task Test_AlternativeRoutingEnabled_ButNotAllowed_NormalRequestSucceeds( + HttpRequestMessage request, MockedRequest mockedRequestOriginal) + { + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + await NoDependenciesAreCalledAsync(); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_ButNotAllowedDueToDoHDisabled_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = false; + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + await Test_AlternativeRoutingEnabled_ButNotAllowed_NormalRequestSucceeds(request, mockedRequestOriginal); + } + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_ButNotAllowedDueToGuestHoleActive_NormalRequestSucceeds() + { + // Arrange + SetActiveAlternativeHost(); + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _guestHoleState.SetState(true); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => new(HttpStatusCode.OK)); + + await Test_AlternativeRoutingEnabled_ButNotAllowed_NormalRequestSucceeds(request, mockedRequestOriginal); + } + + + + [TestMethod] + public async Task Test_AlternativeRoutingEnabled_SecondRequestDoesNotRepeatApiAvailabilityCheck() + { + // Arrange + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw new Exception("Test exception")); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // More arrange + Act + Assert + await InternalTest_AlternativeRoutingEnabled_LastCheckIsOld_NoCache_AlternativeRoutingSucceeds( + request, mockedRequestError1, mockedRequestError2, mockedRequestSuccess); + + // Arrange + HttpRequestMessage request2 = new(HttpMethod.Get, REQUEST_URL); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request2); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request2.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(2); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(2); + await _dnsManager.Received(2).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(0).Generate(Arg.Any()); + await _alternativeHostsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + } + + + + + + + + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task TestOnUserLoggedOut_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + // Arrange + _alternativeHostHandler.OnUserLoggedOut(); + + // Arrange + Act + Assert + await TestOnUserLoggedInOrOut_LastIpAddressOfLastAlternativeHostWorks(exceptionType); + + // Assert + _alternativeRoutingHostGenerator.Received(1).Generate(null); + } + + private async Task TestOnUserLoggedInOrOut_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + await SetVpnStatusAsync(VpnStatus.Disconnected); + _appSettings.DoHEnabled = true; + _alternativeHostsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateAlternativeHostsList()); + _dnsManager.GetAsync(ALTERNATIVE_HOST_3, Arg.Any()) + .Returns(CreateIpAddressList()); + HttpRequestMessage request = new(HttpMethod.Get, REQUEST_URL); + MockedRequest mockedRequestOriginal = _mockHttpMessageHandler + .When(REQUEST_URL) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError1 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_1) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestError2 = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_2) + .Respond(_ => throw CreateException(exceptionType)); + MockedRequest mockedRequestSuccess = _mockHttpMessageHandler + .When(ALTERNATIVE_URL_3) + .Respond(_ => new(HttpStatusCode.OK)); + + // Act + HttpResponseMessage response = await _httpClient.SendAsync(request); + + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(REQUEST_URL, request.RequestUri.ToString()); + _mockHttpMessageHandler.GetMatchCount(mockedRequestOriginal).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestError2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequestSuccess).Should().Be(1); + await _dnsManager.Received(3).GetAsync(Arg.Any(), Arg.Any()); + foreach (string alternativeHosts in CreateAlternativeHostsList()) + { + await _dnsManager.Received(1).GetAsync(alternativeHosts, Arg.Any()); + } + await _dnsManager.Received(1).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(1).ResolveWithoutCacheAsync(CONFIG_API_HOST, Arg.Any()); + _dnsManager.Received(1).GetFromCache(Arg.Any()); + _dnsManager.Received(1).GetFromCache(CONFIG_API_HOST); + _alternativeRoutingHostGenerator.Received(1).Generate(Arg.Any()); + await _alternativeHostsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + [DataRow(typeof(TimeoutException))] + [DataRow(typeof(DnsException))] + [DataRow(typeof(TimeoutRejectedException))] + [DataRow(typeof(AuthenticationException))] + public async Task TestOnUserLoggedIn_LastIpAddressOfLastAlternativeHostWorks(Type exceptionType) + { + // Arrange + _alternativeHostHandler.OnUserLoggedIn(); - HttpResponseMessage result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, _baseAddress)); - result.RequestMessage.RequestUri.Should().Be(_baseAddress); + // Arrange + Act + Assert + await TestOnUserLoggedInOrOut_LastIpAddressOfLastAlternativeHostWorks(exceptionType); + + // Assert + _alternativeRoutingHostGenerator.Received(1).Generate(USER_ID); } } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/DnsHandlerTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/DnsHandlerTest.cs new file mode 100644 index 000000000..404372d6c --- /dev/null +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/DnsHandlerTest.cs @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Api.Handlers; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Networking; +using ProtonVPN.Dns.Contracts; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Api.Tests.Handlers +{ + [TestClass] + public class DnsHandlerTest + { + private const string TEST_URL = "https://protonvpn.com/test"; + + private ILogger _logger; + private IDnsManager _dnsManager; + private MockHttpMessageHandler _mockHttpMessageHandler; + private DnsHandler _dnsHandler; + private HttpClient _httpClient; + + [TestInitialize] + public void TestInitialize() + { + _logger = Substitute.For(); + _dnsManager = Substitute.For(); + _dnsManager.GetAsync(Arg.Any(), Arg.Any()) + .Returns(CreateIpAddressList()); + _mockHttpMessageHandler = new MockHttpMessageHandler(); + _dnsHandler = new(_logger, _dnsManager) { InnerHandler = _mockHttpMessageHandler }; + _httpClient = new(_dnsHandler); + } + + private IList CreateIpAddressList() + { + return new List() + { + new(IPAddress.Parse("192.168.1.1")), + new(IPAddress.Parse("192.168.2.2")), + new(IPAddress.Parse("192.168.3.3")), + }; + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _dnsManager = null; + _mockHttpMessageHandler = null; + _dnsHandler = null; + _httpClient = null; + } + + [TestMethod] + public async Task Test_WithIpAddress() + { + string url = "http://127.0.0.1/auth"; + HttpRequestMessage request = new(HttpMethod.Get, url); + MockedRequest mockedRequest = _mockHttpMessageHandler.When("http://127.0.0.1/auth") + .Respond(_ => new(HttpStatusCode.OK)); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(url, request.RequestUri.ToString()); + await AssertDnsManagerWasNotCalledAsync(); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + } + + private async Task AssertDnsManagerWasNotCalledAsync() + { + await _dnsManager.Received(0).GetAsync(Arg.Any(), Arg.Any()); + await _dnsManager.Received(0).ResolveWithoutCacheAsync(Arg.Any(), Arg.Any()); + _dnsManager.Received(0).GetFromCache(Arg.Any()); + } + + [TestMethod] + public async Task Test_WithDomain() + { + HttpRequestMessage request = new(HttpMethod.Get, TEST_URL); + MockedRequest mockedRequest = _mockHttpMessageHandler.When("https://192.168.1.1/test") + .Respond(_ => new(HttpStatusCode.OK)); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("https://192.168.1.1/test", request.RequestUri.ToString()); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest).Should().Be(1); + } + + [TestMethod] + public async Task Test_WithDomainAndFirstIpAddressFails() + { + HttpRequestMessage request = new(HttpMethod.Get, TEST_URL); + MockedRequest mockedRequest1 = _mockHttpMessageHandler.When("https://192.168.1.1/test") + .Respond(_ => throw new("Test exception")); + MockedRequest mockedRequest2 = _mockHttpMessageHandler.When("https://192.168.2.2/test") + .Respond(_ => new(HttpStatusCode.OK)); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("https://192.168.2.2/test", request.RequestUri.ToString()); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequest2).Should().Be(1); + } + + [TestMethod] + public async Task Test_WithDomainAndFirstTwoIpAddressesFail() + { + HttpRequestMessage request = new(HttpMethod.Get, TEST_URL); + MockedRequest mockedRequest1 = _mockHttpMessageHandler.When("https://192.168.1.1/test") + .Respond(_ => throw new("Test exception")); + MockedRequest mockedRequest2 = _mockHttpMessageHandler.When("https://192.168.2.2/test") + .Respond(_ => throw new("Test exception")); + MockedRequest mockedRequest3 = _mockHttpMessageHandler.When("https://192.168.3.3/test") + .Respond(_ => new(HttpStatusCode.OK)); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("https://192.168.3.3/test", request.RequestUri.ToString()); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequest2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequest3).Should().Be(1); + } + + [TestMethod] + public async Task Test_WithDomainAndAllIpAddressesFail() + { + string expectedExceptionMessage = "Test exception 3"; + HttpRequestMessage request = new(HttpMethod.Get, TEST_URL); + MockedRequest mockedRequest1 = _mockHttpMessageHandler.When("https://192.168.1.1/test") + .Respond(_ => throw new("Test exception 1")); + MockedRequest mockedRequest2 = _mockHttpMessageHandler.When("https://192.168.2.2/test") + .Respond(_ => throw new("Test exception 2")); + MockedRequest mockedRequest3 = _mockHttpMessageHandler.When("https://192.168.3.3/test") + .Respond(_ => throw new(expectedExceptionMessage)); + + Exception exception = await Assert.ThrowsExceptionAsync( + async () => await _httpClient.SendAsync(request)); + + Assert.AreEqual(expectedExceptionMessage, exception.Message); + Assert.AreEqual(TEST_URL, request.RequestUri.ToString()); + await _dnsManager.Received(1).GetAsync(Arg.Any(), Arg.Any()); + _mockHttpMessageHandler.GetMatchCount(mockedRequest1).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequest2).Should().Be(1); + _mockHttpMessageHandler.GetMatchCount(mockedRequest3).Should().Be(1); + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/OutdatedAppHandlerTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/OutdatedAppHandlerTest.cs index b395e8524..b2850065f 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/OutdatedAppHandlerTest.cs +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/OutdatedAppHandlerTest.cs @@ -26,6 +26,7 @@ using ProtonVPN.Api.Contracts.Common; using ProtonVPN.Api.Handlers; using ProtonVPN.Api.Tests.Deserializers; +using ProtonVPN.Api.Tests.Mocks; namespace ProtonVPN.Api.Tests.Handlers { @@ -41,10 +42,12 @@ public async Task ItShouldInvokeOutdatedAppEvent(int code) int called = 0; MockOfRetryingHandler mockOfRetryingHandler = new(); - MockOfBaseResponseMessageDeserializer mockOfBaseResponseDeserializer = new(); - mockOfBaseResponseDeserializer.ExpectedBaseResponse = new BaseResponse() { Code = code }; + MockOfBaseResponseMessageDeserializer mockOfBaseResponseDeserializer = new() + { + ExpectedBaseResponse = new BaseResponse() { Code = code } + }; mockOfRetryingHandler.SetResponseAsSuccess(code); - OutdatedAppHandler handler = new(mockOfBaseResponseDeserializer, mockOfRetryingHandler); + OutdatedAppHandler handler = new(mockOfBaseResponseDeserializer) { InnerHandler = mockOfRetryingHandler }; handler.AppOutdated += (sender, args) => called++; HttpClient httpClient = new(handler) {BaseAddress = new Uri("http://127.0.0.1")}; diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/Retries/RetryingHandlerTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/Retries/RetryingHandlerTest.cs index e5a80de88..350fbb89d 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/Retries/RetryingHandlerTest.cs +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/Retries/RetryingHandlerTest.cs @@ -28,6 +28,7 @@ using Polly.Timeout; using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Handlers.Retries; +using ProtonVPN.Api.Tests.Mocks; using ProtonVPN.Common.Logging; using RichardSzalay.MockHttp; @@ -154,7 +155,7 @@ private IRetryPolicyProvider GetRetryPolicyProvider(int retryCount) private HttpClient GetHttpClient(int maxRetries, MockHttpMessageHandler mockHttpMessageHandler) { MockOfLoggingHandler loggingHandler = new(mockHttpMessageHandler); - RetryingHandler handler = new(loggingHandler, GetRetryPolicyProvider(maxRetries)); + RetryingHandler handler = new(GetRetryPolicyProvider(maxRetries)) { InnerHandler = loggingHandler }; return new(handler) { BaseAddress = new Uri(BASE_API_URL) }; } } diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilderTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilderTest.cs new file mode 100644 index 000000000..13e526afa --- /dev/null +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilderTest.cs @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Api.Handlers.StackBuilders; +using ProtonVPN.Api.Tests.Mocks; + +namespace ProtonVPN.Api.Tests.Handlers.StackBuilders +{ + [TestClass] + public class CompleteHttpMessageHandlerStackBuilderTest + { + [TestMethod] + public void Test_WithNullDelegatingHandler() + { + MockOfWebRequestHandler mockOfWebRequestHandler = new(); + + CompleteHttpMessageHandlerStackBuilder completeStackBuilder = new(mockOfWebRequestHandler); + HttpMessageHandler resultHandler = completeStackBuilder.Build(); + + Assert.AreEqual(resultHandler, mockOfWebRequestHandler); + } + + [TestMethod] + public void Test_FailsWithNullHttpMessageHandler() + { + Action action = () => new CompleteHttpMessageHandlerStackBuilder(null, null); + + Assert.ThrowsException(action); + } + + [TestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(2)] + [DataRow(3)] + [DataRow(12)] + public void Test_WithDelegatingHandlers(int numDelegatingHandlers) + { + // Arrange + List mockOfDelegatingHandlers = new(); + for (int i = 0; i < numDelegatingHandlers; i++) + { + mockOfDelegatingHandlers.Add(new MockOfDelegatingHandler()); + } + MockOfWebRequestHandler mockOfWebRequestHandler = new(); + + // Act + CompleteHttpMessageHandlerStackBuilder completeStackBuilder = new(mockOfWebRequestHandler, + mockOfDelegatingHandlers.Select(dh => (DelegatingHandler)dh).ToList()); + HttpMessageHandler resultHandler = completeStackBuilder.Build(); + + // Assert + HttpMessageHandler handlerToEvaluate = resultHandler; + foreach (MockOfDelegatingHandler mockOfDelegatingHandler in mockOfDelegatingHandlers) + { + Assert.AreEqual(mockOfDelegatingHandler, handlerToEvaluate); + handlerToEvaluate = mockOfDelegatingHandler.InnerHandler; + } + Assert.AreEqual(mockOfWebRequestHandler, handlerToEvaluate); + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/HttpMessageHandlerStackBuilderTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/HttpMessageHandlerStackBuilderTest.cs new file mode 100644 index 000000000..d1a1ba3eb --- /dev/null +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/StackBuilders/HttpMessageHandlerStackBuilderTest.cs @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Api.Handlers.StackBuilders; +using ProtonVPN.Api.Tests.Mocks; + +namespace ProtonVPN.Api.Tests.Handlers.StackBuilders +{ + [TestClass] + public class HttpMessageHandlerStackBuilderTest + { + [TestMethod] + public void TestAddLastHandler() + { + MockOfWebRequestHandler mockOfWebRequestHandler = new(); + + HttpMessageHandler resultHandler = new HttpMessageHandlerStackBuilder() + .AddLastHandler(mockOfWebRequestHandler) + .Build(); + + Assert.AreEqual(resultHandler, mockOfWebRequestHandler); + } + + [TestMethod] + public void TestAddLastHandler_FailsWithDelegatingHandler() + { + MockOfDelegatingHandler mockOfDelegatingHandler = new(); + + Action action = () => new HttpMessageHandlerStackBuilder() + .AddLastHandler(mockOfDelegatingHandler); + + Assert.ThrowsException(action); + } + + [TestMethod] + public void TestAddLastHandler_FailsWithNullArgument() + { + Action action = () => new HttpMessageHandlerStackBuilder() + .AddLastHandler(null); + + Assert.ThrowsException(action); + } + + [TestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(2)] + [DataRow(3)] + [DataRow(12)] + public void TestAddDelegatingHandler(int numDelegatingHandlers) + { + // Arrange + List mockOfDelegatingHandlers = new(); + for (int i = 0; i < numDelegatingHandlers; i++) + { + mockOfDelegatingHandlers.Add(new MockOfDelegatingHandler()); + } + MockOfWebRequestHandler mockOfWebRequestHandler = new(); + + // Act + HttpMessageHandlerStackBuilder stackBuilder = new HttpMessageHandlerStackBuilder(); + foreach (MockOfDelegatingHandler mockOfDelegatingHandler in mockOfDelegatingHandlers) + { + stackBuilder = stackBuilder.AddDelegatingHandler(mockOfDelegatingHandler); + } + HttpMessageHandler resultHandler = stackBuilder + .AddLastHandler(mockOfWebRequestHandler) + .Build(); + + // Assert + HttpMessageHandler handlerToEvaluate = resultHandler; + foreach (MockOfDelegatingHandler mockOfDelegatingHandler in mockOfDelegatingHandlers) + { + Assert.AreEqual(mockOfDelegatingHandler, handlerToEvaluate); + handlerToEvaluate = mockOfDelegatingHandler.InnerHandler; + } + Assert.AreEqual(mockOfWebRequestHandler, handlerToEvaluate); + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/UnauthorizedResponseHandlerTest.cs b/src/Api/ProtonVPN.Api.Tests/Handlers/UnauthorizedResponseHandlerTest.cs index 2d1d4e233..4661bbce1 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/UnauthorizedResponseHandlerTest.cs +++ b/src/Api/ProtonVPN.Api.Tests/Handlers/UnauthorizedResponseHandlerTest.cs @@ -30,12 +30,13 @@ using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Auth; using ProtonVPN.Api.Handlers; +using ProtonVPN.Api.Tests.Mocks; using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Logging; using ProtonVPN.Common.Logging.Categorization.Events.UserLogs; -using ProtonVPN.Core.Abstract; +using ProtonVPN.Core.Models; using ProtonVPN.Core.Settings; -using ProtonVPN.Test.Common.Breakpoints; +using ProtonVPN.Tests.Common.Breakpoints; using RichardSzalay.MockHttp; namespace ProtonVPN.Api.Tests.Handlers @@ -62,7 +63,7 @@ public class UnauthorizedResponseHandlerTest private readonly Uri _baseAddress = new(BASE_API_URL); private ITokenClient _tokenClient; - private ITokenStorage _tokenStorage; + private IAppSettings _appSettings; private IUserStorage _userStorage; private ILogger _logger; private MockHttpMessageHandler _innerHandler; @@ -74,13 +75,13 @@ public void TestInitialize() _tokenClient.RefreshTokenAsync(Arg.Any()) .Returns(ApiResponseResult.Ok(new HttpResponseMessage(), new())); - _tokenStorage = Substitute.For(); - _tokenStorage.AccessToken.Returns(ACCESS_TOKEN); - _tokenStorage.RefreshToken.Returns(REFRESH_TOKEN); - _tokenStorage.Uid.Returns("User ID"); + _appSettings = Substitute.For(); + _appSettings.AccessToken.Returns(ACCESS_TOKEN); + _appSettings.RefreshToken.Returns(REFRESH_TOKEN); + _appSettings.Uid.Returns("User ID"); _userStorage = Substitute.For(); - _userStorage.User().Returns(new Core.Models.User { Username = "test" }); + _userStorage.GetUser().Returns(new User { Username = "test" }); _logger = Substitute.For(); @@ -129,7 +130,7 @@ public async Task SendAsync_ShouldCall_TokenClient_RefreshTokenAsync_WhenUnautho [TestMethod] public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenRefreshTokenIsNull() { - _tokenStorage.RefreshToken.Returns((string)null); + _appSettings.RefreshToken.Returns((string)null); await SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenCurrentTokenIsInvalid(); } @@ -154,7 +155,7 @@ private async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenCur [TestMethod] public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenRefreshTokenIsEmpty() { - _tokenStorage.RefreshToken.Returns(string.Empty); + _appSettings.RefreshToken.Returns(string.Empty); await SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenCurrentTokenIsInvalid(); } @@ -162,7 +163,7 @@ public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenRefr [TestMethod] public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenTokenUserIdIsNull() { - _tokenStorage.Uid.Returns((string)null); + _appSettings.Uid.Returns((string)null); await SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenCurrentTokenIsInvalid(); } @@ -170,7 +171,7 @@ public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenToke [TestMethod] public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenTokenUserIdIsEmpty() { - _tokenStorage.Uid.Returns(string.Empty); + _appSettings.Uid.Returns(string.Empty); await SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenCurrentTokenIsInvalid(); } @@ -242,8 +243,8 @@ public async Task SendAsync_ShouldSet_TokenStorage_Tokens() await client.SendAsync(request); // Assert - _tokenStorage.AccessToken.Should().Be(NEW_ACCESS_TOKEN); - _tokenStorage.RefreshToken.Should().Be(NEW_REFRESH_TOKEN); + _appSettings.AccessToken.Should().Be(NEW_ACCESS_TOKEN); + _appSettings.RefreshToken.Should().Be(NEW_REFRESH_TOKEN); } [TestMethod] @@ -301,8 +302,8 @@ public async Task SendAsync_ShouldLimit_RefreshRequests_ToOne() Breakpoint tokenClientBreakpoint = breakpointTokenClient.Breakpoint; MockOfHumanVerificationHandler humanVerificationHandler = new() { InnerHandler = breakpointHandler }; - UnauthorizedResponseHandler handler = new(humanVerificationHandler, breakpointTokenClient, _tokenStorage, - _userStorage, _logger); + UnauthorizedResponseHandler handler = new(breakpointTokenClient, _appSettings, _userStorage, _logger) + { InnerHandler = humanVerificationHandler }; HttpClient client = new(handler) { BaseAddress = _baseAddress }; _tokenClient.RefreshTokenAsync(Arg.Any()) @@ -498,13 +499,13 @@ public async Task SendAsync_ShouldRetryWithNewToken_WhenRefreshedWhileRequesting private UnauthorizedResponseHandler GetUnauthorizedResponseHandler(HumanVerificationHandlerBase handler) { - return new(handler, _tokenClient, _tokenStorage, _userStorage, _logger); + return new(_tokenClient, _appSettings, _userStorage, _logger) { InnerHandler = handler }; } private UnauthorizedResponseHandler GetUnauthorizedResponseHandlerWithBreakpoint(BreakpointHandler breakpointHandler, ITokenClient tokenClient) { MockOfHumanVerificationHandler handler = new() { InnerHandler = breakpointHandler }; - return new(handler, tokenClient, _tokenStorage, _userStorage, _logger); + return new(tokenClient, _appSettings, _userStorage, _logger) { InnerHandler = handler }; } #region Helpers diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfCancellingHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs similarity index 96% rename from src/Api/ProtonVPN.Api.Tests/Handlers/MockOfCancellingHandler.cs rename to src/Api/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs index c001f8598..3cd776371 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfCancellingHandler.cs +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs @@ -20,7 +20,7 @@ using ProtonVPN.Api.Handlers; using RichardSzalay.MockHttp; -namespace ProtonVPN.Api.Tests.Handlers +namespace ProtonVPN.Api.Tests.Mocks { public class MockOfCancellingHandler : CancellingHandlerBase { diff --git a/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs new file mode 100644 index 000000000..adf8bfbcf --- /dev/null +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Net.Http; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfDelegatingHandler : DelegatingHandler + { + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfHumanVerificationHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs similarity index 96% rename from src/Api/ProtonVPN.Api.Tests/Handlers/MockOfHumanVerificationHandler.cs rename to src/Api/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs index 2a247bdb0..99ec1ab60 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfHumanVerificationHandler.cs +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs @@ -20,7 +20,7 @@ using ProtonVPN.Api.Handlers; using RichardSzalay.MockHttp; -namespace ProtonVPN.Api.Tests.Handlers +namespace ProtonVPN.Api.Tests.Mocks { public class MockOfHumanVerificationHandler : HumanVerificationHandlerBase { diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfLoggingHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs similarity index 96% rename from src/Api/ProtonVPN.Api.Tests/Handlers/MockOfLoggingHandler.cs rename to src/Api/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs index 5920eb8db..f771aa029 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfLoggingHandler.cs +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs @@ -20,7 +20,7 @@ using ProtonVPN.Api.Handlers; using RichardSzalay.MockHttp; -namespace ProtonVPN.Api.Tests.Handlers +namespace ProtonVPN.Api.Tests.Mocks { public class MockOfLoggingHandler : LoggingHandlerBase { diff --git a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfRetryingHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs similarity index 97% rename from src/Api/ProtonVPN.Api.Tests/Handlers/MockOfRetryingHandler.cs rename to src/Api/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs index 6dc7c028e..187ef07fd 100644 --- a/src/Api/ProtonVPN.Api.Tests/Handlers/MockOfRetryingHandler.cs +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs @@ -24,7 +24,7 @@ using ProtonVPN.Api.Handlers.Retries; using RichardSzalay.MockHttp; -namespace ProtonVPN.Api.Tests.Handlers +namespace ProtonVPN.Api.Tests.Mocks { public class MockOfRetryingHandler : RetryingHandlerBase { diff --git a/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs new file mode 100644 index 000000000..51601d734 --- /dev/null +++ b/src/Api/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Net.Http; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfWebRequestHandler : WebRequestHandler + { + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/ProtonVPN.Api.Tests.csproj b/src/Api/ProtonVPN.Api.Tests/ProtonVPN.Api.Tests.csproj index 167837b45..0fbb95b33 100644 --- a/src/Api/ProtonVPN.Api.Tests/ProtonVPN.Api.Tests.csproj +++ b/src/Api/ProtonVPN.Api.Tests/ProtonVPN.Api.Tests.csproj @@ -1,6 +1,5 @@  - Debug @@ -44,42 +43,12 @@ true - - ..\..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - ..\..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - ..\..\..\packages\Polly.7.2.0\lib\net472\Polly.dll - - - ..\..\..\packages\RichardSzalay.MockHttp.5.0.0\lib\net45\RichardSzalay.MockHttp.dll - - - ..\..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - @@ -93,21 +62,25 @@ - - - - + + + + + + + + + - PreserveNewest @@ -122,9 +95,13 @@ - - {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + + + {a0da4200-6643-4f2c-8450-65b8ce8a5576} + ProtonVPN.Tests.Common {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} @@ -143,15 +120,38 @@ ProtonVPN.Core - + + + 1.8.9 + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 13.0.1 + + + 4.3.0 + + + 7.2.0 + + + 5.0.0 + + + 5.0.0 + + + 4.5.4 + + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/app.config b/src/Api/ProtonVPN.Api.Tests/app.config index 080978b36..c92446e50 100644 --- a/src/Api/ProtonVPN.Api.Tests/app.config +++ b/src/Api/ProtonVPN.Api.Tests/app.config @@ -1,19 +1,19 @@  - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api.Tests/packages.config b/src/Api/ProtonVPN.Api.Tests/packages.config deleted file mode 100644 index 38cc700d1..000000000 --- a/src/Api/ProtonVPN.Api.Tests/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/ApiClient.cs b/src/Api/ProtonVPN.Api/ApiClient.cs index 06f7a5fe1..b45778253 100644 --- a/src/Api/ProtonVPN.Api/ApiClient.cs +++ b/src/Api/ProtonVPN.Api/ApiClient.cs @@ -36,8 +36,8 @@ using ProtonVPN.Api.Contracts.VpnSessions; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.ApiLogs; using ProtonVPN.Common.OS.Net.Http; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Settings; namespace ProtonVPN.Api @@ -51,12 +51,12 @@ public class ApiClient : BaseApiClient, IApiClient private readonly HttpClient _noCacheClient; public ApiClient( - IHttpClientFactory httpClientFactory, + IApiHttpClientFactory httpClientFactory, ILogger logger, - ITokenStorage tokenStorage, + IAppSettings appSettings, IApiAppVersion appVersion, IAppLanguageCache appLanguageCache, - Config config) : base(logger, appVersion, tokenStorage, appLanguageCache, config) + Config config) : base(logger, appVersion, appSettings, appLanguageCache, config) { _client = httpClientFactory.GetApiHttpClientWithCache(); _noCacheClient = httpClientFactory.GetApiHttpClientWithoutCache(); @@ -253,8 +253,13 @@ private async Task> SendRequest(HttpClient httpClient, H return Logged(await GetApiResponseResult(response), logDescription); } } - catch (Exception e) when (e.IsApiCommunicationException()) + catch (Exception e) { + if (!e.IsApiCommunicationException()) + { + Logger.Error("An exception occurred in an API request " + + "that is not related with its communication.", e); + } throw new HttpRequestException(e.Message, e); } } diff --git a/src/Api/ProtonVPN.Api/ApiExceptionHelper.cs b/src/Api/ProtonVPN.Api/ApiExceptionHelper.cs index 1b0606a4d..ae2b6ab0a 100644 --- a/src/Api/ProtonVPN.Api/ApiExceptionHelper.cs +++ b/src/Api/ProtonVPN.Api/ApiExceptionHelper.cs @@ -20,9 +20,9 @@ using System; using System.Net.Http; using System.Net.Sockets; -using System.Security.Authentication; using Newtonsoft.Json; using Polly.Timeout; +using ProtonVPN.Dns.Contracts.Exceptions; namespace ProtonVPN.Api { @@ -30,13 +30,8 @@ public static class ApiExceptionHelper { public static bool IsApiCommunicationException(this Exception ex) { - return ex is HttpRequestException or JsonException or OperationCanceledException or TimeoutRejectedException - or SocketException; - } - - public static bool IsPotentialBlocking(this Exception e) - { - return e is TimeoutException or TimeoutRejectedException || e.GetBaseException() is AuthenticationException; + return ex is HttpRequestException or JsonException or OperationCanceledException + or TimeoutRejectedException or SocketException or DnsException; } } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/ApiHttpClientFactory.cs b/src/Api/ProtonVPN.Api/ApiHttpClientFactory.cs new file mode 100644 index 000000000..42a69841c --- /dev/null +++ b/src/Api/ProtonVPN.Api/ApiHttpClientFactory.cs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Net.Http; +using ProtonVPN.Api.Handlers; +using ProtonVPN.Api.Handlers.StackBuilders; +using ProtonVPN.Api.Handlers.Retries; +using ProtonVPN.Api.Handlers.TlsPinning; +using ProtonVPN.Common.Configuration; + +namespace ProtonVPN.Api +{ + public class ApiHttpClientFactory : IApiHttpClientFactory + { + private readonly Config _config; + private readonly HttpMessageHandler _innerHandler; + + public ApiHttpClientFactory(Config config, + AlternativeHostHandler alternativeHostHandler, + CancellingHandlerBase cancellingHandlerBase, + UnauthorizedResponseHandler unauthorizedResponseHandler, + HumanVerificationHandlerBase humanVerificationHandlerBase, + OutdatedAppHandler outdatedAppHandler, + RetryingHandlerBase retryingHandlerBase, + DnsHandler dnsHandler, + LoggingHandlerBase loggingHandlerBase, + CertificateHandler certificateHandler) + { + _config = config; + + _innerHandler = new HttpMessageHandlerStackBuilder() + .AddDelegatingHandler(alternativeHostHandler) + .AddDelegatingHandler(cancellingHandlerBase) + .AddDelegatingHandler(unauthorizedResponseHandler) + .AddDelegatingHandler(humanVerificationHandlerBase) + .AddDelegatingHandler(outdatedAppHandler) + .AddDelegatingHandler(retryingHandlerBase) + .AddDelegatingHandler(dnsHandler) + .AddDelegatingHandler(loggingHandlerBase) + .AddLastHandler(certificateHandler) + .Build(); + } + + public HttpClient GetApiHttpClientWithoutCache() + { + HttpClient client = GetApiHttpClientWithCache(); + client.DefaultRequestHeaders.ConnectionClose = true; + return client; + } + + public HttpClient GetApiHttpClientWithCache() + { + return new(_innerHandler) { BaseAddress = new Uri(_config.Urls.ApiUrl) }; + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/BaseApiClient.cs b/src/Api/ProtonVPN.Api/BaseApiClient.cs index 5b1c724e0..a52dedd1d 100644 --- a/src/Api/ProtonVPN.Api/BaseApiClient.cs +++ b/src/Api/ProtonVPN.Api/BaseApiClient.cs @@ -30,17 +30,17 @@ using ProtonVPN.Common.Logging; using ProtonVPN.Common.Logging.Categorization.Events.ApiLogs; using ProtonVPN.Common.OS.Net.Http; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Settings; namespace ProtonVPN.Api { public class BaseApiClient : IClientBase { + protected ILogger Logger { get; } + protected IAppSettings AppSettings { get; } + private readonly JsonSerializer _jsonSerializer = new(); - private readonly ILogger _logger; private readonly IApiAppVersion _appVersion; - protected readonly ITokenStorage TokenStorage; private readonly string _apiVersion; private readonly IAppLanguageCache _appLanguageCache; @@ -49,13 +49,13 @@ public class BaseApiClient : IClientBase public BaseApiClient( ILogger logger, IApiAppVersion appVersion, - ITokenStorage tokenStorage, + IAppSettings appSettings, IAppLanguageCache appLanguageCache, Config config) { - _logger = logger; + Logger = logger; + AppSettings = appSettings; _appVersion = appVersion; - TokenStorage = tokenStorage; _apiVersion = config.ApiVersion; _appLanguageCache = appLanguageCache; } @@ -132,7 +132,7 @@ protected HttpRequestMessage GetRequest(HttpMethod method, string requestUri) protected HttpRequestMessage GetAuthorizedRequest(HttpMethod method, string requestUri) { - return GetAuthorizedRequest(method, requestUri, TokenStorage.AccessToken, TokenStorage.Uid); + return GetAuthorizedRequest(method, requestUri, AppSettings.AccessToken, AppSettings.Uid); } protected HttpRequestMessage GetAuthorizedRequest(HttpMethod method, string requestUri, string accessToken, string uid) @@ -180,7 +180,7 @@ protected ApiResponseResult Logged(ApiResponseResult result, string mes { if (result.Failure) { - _logger.Error($"API: {(!string.IsNullOrEmpty(message) ? message : "Request")} failed: {result.Error}"); + Logger.Error($"API: {(message.IsNullOrEmpty() ? "Request" : message)} failed: {result.Error}"); } return result; diff --git a/src/Api/ProtonVPN.Api/Handlers/AlternativeHostHandler.cs b/src/Api/ProtonVPN.Api/Handlers/AlternativeHostHandler.cs index a26779be3..0ba2de95c 100644 --- a/src/Api/ProtonVPN.Api/Handlers/AlternativeHostHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/AlternativeHostHandler.cs @@ -19,65 +19,65 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Net.Http; +using System.Security.Authentication; using System.Threading; using System.Threading.Tasks; -using ProtonVPN.Api.Contracts; -using ProtonVPN.Common.Abstract; +using Polly.Timeout; +using ProtonVPN.Api.Contracts.Exceptions; using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Logging; using ProtonVPN.Common.Logging.Categorization.Events.ApiLogs; -using ProtonVPN.Common.Logging.Categorization.Events.AppLogs; -using ProtonVPN.Common.Threading; +using ProtonVPN.Common.Networking; using ProtonVPN.Common.Vpn; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Auth; -using ProtonVPN.Core.OS.Net.DoH; using ProtonVPN.Core.Settings; using ProtonVPN.Core.Vpn; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.AlternativeRouting; +using ProtonVPN.Dns.Contracts.Exceptions; namespace ProtonVPN.Api.Handlers { public class AlternativeHostHandler : DelegatingHandler, ILoggedInAware, ILogoutAware { + public const string API_PING_TEST_PATH = "tests/ping"; + private const string NO_ALTERNATIVE_HOSTS_ERROR_MESSAGE = "No alternative hosts exist. Alternative routing failed."; + private const string ALL_ALTERNATIVE_HOSTS_FAILED_ERROR_MESSAGE = "All alternative hosts failed. Alternative routing failed."; + private readonly ILogger _logger; - private readonly DohClients _dohClients; - private readonly MainHostname _mainHostname; + private readonly IDnsManager _dnsManager; + private readonly IAlternativeRoutingHostGenerator _alternativeRoutingHostGenerator; + private readonly IAlternativeHostsManager _alternativeHostsManager; private readonly IAppSettings _appSettings; private readonly GuestHoleState _guestHoleState; - private readonly ITokenStorage _tokenStorage; - private readonly IApiHostProvider _apiHostProvider; - private readonly SingleAction _fetchProxies; + private readonly string _defaultApiHost; + private readonly TimeSpan _alternativeRoutingCheckInterval; + private readonly SemaphoreSlim _semaphore = new(1, 1); - private string _activeBackendHost; - private readonly string _apiHost; - private bool _isDisconnected; + private bool _isDisconnected = true; private bool _isUserLoggedIn; + private DateTime? _lastAlternativeRoutingCheckDateUtc; + private string _activeAlternativeHost; public AlternativeHostHandler( - CancellingHandlerBase cancellingHandler, ILogger logger, - DohClients dohClients, - MainHostname mainHostname, + IDnsManager dnsManager, + IAlternativeRoutingHostGenerator alternativeRoutingHostGenerator, + IAlternativeHostsManager alternativeHostsManager, IAppSettings appSettings, GuestHoleState guestHoleState, - ITokenStorage tokenStorage, - IApiHostProvider apiHostProvider, - Config config) + IConfiguration configuration) { _logger = logger; - _dohClients = dohClients; - _mainHostname = mainHostname; + _dnsManager = dnsManager; + _alternativeRoutingHostGenerator = alternativeRoutingHostGenerator; + _alternativeHostsManager = alternativeHostsManager; _appSettings = appSettings; _guestHoleState = guestHoleState; - _tokenStorage = tokenStorage; - _apiHostProvider = apiHostProvider; - _apiHost = new Uri(config.Urls.ApiUrl).Host; - _activeBackendHost = _apiHost; - _fetchProxies = new SingleAction(FetchProxies); - - InnerHandler = cancellingHandler; + _defaultApiHost = new Uri(configuration.Urls.ApiUrl).Host; + _alternativeRoutingCheckInterval = configuration.AlternativeRoutingCheckInterval; } public async Task OnVpnStateChanged(VpnStateChangedEventArgs e) @@ -85,182 +85,359 @@ public async Task OnVpnStateChanged(VpnStateChangedEventArgs e) _isDisconnected = e.State.Status == VpnStatus.Disconnected; } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken token) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - if (!_appSettings.DoHEnabled || _guestHoleState.Active) + if (IsAlternativeRoutingEnabled()) { - ResetBackendHost(); - return await SendInternalAsync(request, token); + if (IsAlternativeRoutingAllowed()) + { + if (IsLastAlternativeRoutingCheckDateNullOrTooOld()) + { + bool isApiAvailable = await IsApiAvailableAsync(request, cancellationToken); + if (isApiAvailable) + { + await DisableAlternativeRoutingAsync(); + } + else + { + return await SendRequestWithActiveAlternativeHostAsync(request, cancellationToken); + } + } + else + { + return await SendRequestWithActiveAlternativeHostAsync(request, cancellationToken); + } + } + else + { + await DisableAlternativeRoutingAsync(); + } } - if (_apiHostProvider.IsProxyActive()) + return await TrySendRequestAsync(request, cancellationToken); + } + + private bool IsAlternativeRoutingEnabled() + { + return !_activeAlternativeHost.IsNullOrEmpty(); + } + + private async Task SendRequestWithActiveAlternativeHostAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + string alternativeHost = _activeAlternativeHost; + if (!alternativeHost.IsNullOrEmpty()) { try { - _activeBackendHost = _apiHostProvider.GetHost(); - _logger.Info($"Sending request using {_activeBackendHost}"); - - return await SendInternalAsync(request, token); + HttpResponseMessage httpResponseMessage = await SendRequestWithActiveAlternativeHostAsync( + alternativeHost, request, cancellationToken); + return httpResponseMessage; } - catch (Exception e) when (e.IsPotentialBlocking()) + catch (Exception ex) { - _logger.Info($"Request failed while DoH active. Host: {_activeBackendHost}"); + _logger.Error($"Alternative host '{alternativeHost}' failed.", ex); + } + } - ResetBackendHost(); - return await SendAsync(request, token); + await DisableAlternativeRoutingAsync(); + return await TrySendRequestAsync(request, cancellationToken); + } + + private async Task SendRequestWithActiveAlternativeHostAsync(string alternativeHost, + HttpRequestMessage request, CancellationToken cancellationToken) + { + IList alternativeHostIpAddresses = await _dnsManager.GetAsync(alternativeHost, cancellationToken); + ThrowIfAlternativeHostHasNoIpAddresses(alternativeHost, alternativeHostIpAddresses); + foreach (IpAddress alternativeHostIpAddress in alternativeHostIpAddresses) + { + try + { + HttpResponseMessage httpResponseMessage = await SendRequestToAlternativeHostAsync( + alternativeHostIpAddress, alternativeHost, request, cancellationToken); + return httpResponseMessage; + } + catch (Exception ex) + { + _logger.Error($"Alternative host '{alternativeHost}' with IP address " + + $"'{alternativeHostIpAddress}' failed.", ex); } } + throw new AlternativeRoutingException($"Alternative host '{alternativeHost}' failed."); + } + + private bool IsAlternativeRoutingAllowed() + { + return _isDisconnected && !_guestHoleState.Active && IsAlternativeRoutingSettingEnabled(); + } + + private bool IsAlternativeRoutingSettingEnabled() + { + return _appSettings.DoHEnabled; + } + private async Task TrySendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage httpResponseMessage; try { - return await SendInternalAsync(request, token); + httpResponseMessage = await SendRequestAsync(request, cancellationToken); } - catch (Exception e) when (_isDisconnected && e.IsPotentialBlocking()) + catch (Exception ex) when (IsAlternativeRoutingAllowed() && IsPotentialBlockingException(ex)) { - _logger.Info("Request failed due to potentially not reachable api."); - - await _fetchProxies.Run(); - - if (_appSettings.AlternativeApiBaseUrls.Count == 0) + bool isApiAvailable = await IsApiAvailableAsync(request, cancellationToken); + if (isApiAvailable) { - throw; + httpResponseMessage = await SendOriginalRequestAndRetryWithAlternativeRoutingAsync(request, cancellationToken); } - - if (await IsApiReachable(request, token)) + else { - _logger.Info("Ping success, retrying original request."); - - try - { - return await SendInternalAsync(request, token); - } - catch (Exception ex) when (ex.IsPotentialBlocking()) - { - throw; - } + httpResponseMessage = await SendRequestWithAlternativeRoutingAsync(request, cancellationToken); } + } + return httpResponseMessage; + } + + private async Task SendOriginalRequestAndRetryWithAlternativeRoutingAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await SendRequestAsync(request, cancellationToken); + } + catch (Exception ex) when (IsAlternativeRoutingAllowed() && IsPotentialBlockingException(ex)) + { + httpResponseMessage = await SendRequestWithAlternativeRoutingAsync(request, cancellationToken); + } + return httpResponseMessage; + } - Result alternativeResult = await TryAlternativeHosts(request, token); - if (alternativeResult.Success) + public bool IsPotentialBlockingException(Exception ex) + { + return ex is TimeoutException or TimeoutRejectedException or DnsException + || ex.GetBaseException() is AuthenticationException; + } + + private bool IsLastAlternativeRoutingCheckDateNullOrTooOld() + { + return _lastAlternativeRoutingCheckDateUtc is null || + _lastAlternativeRoutingCheckDateUtc < (DateTime.UtcNow - _alternativeRoutingCheckInterval); + } + + private async Task IsApiAvailableAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _logger.Info("Checking for API availability."); + IList defaultApiIpAddresses = await _dnsManager.ResolveWithoutCacheAsync(_defaultApiHost, cancellationToken); + if (defaultApiIpAddresses.IsNullOrEmpty()) + { + defaultApiIpAddresses = _dnsManager.GetFromCache(_defaultApiHost); + if (defaultApiIpAddresses.IsNullOrEmpty()) { - return alternativeResult.Value; + await UpdateLastAlternativeRoutingCheckDateAsync(); + _logger.Warn("The API is unavailable due to a failure in the DNS step. " + + "No IP addresses were able to be resolved or fetched from cache."); + return false; } + } - throw; + bool isApiPingSuccessful = await SendApiPingRequestAsync(request, cancellationToken); + await UpdateLastAlternativeRoutingCheckDateAsync(); + LogApiAvailabilityCheckResult(isApiPingSuccessful); + return isApiPingSuccessful; + } + + private void LogApiAvailabilityCheckResult(bool isApiPingSuccessful) + { + if (isApiPingSuccessful) + { + _logger.Info("The API availability check was successful."); + } + else + { + _logger.Warn("The API is unavailable due to a request failure."); } } - private async Task IsApiReachable(HttpRequestMessage request, CancellationToken token) + private async Task UpdateLastAlternativeRoutingCheckDateAsync() { + await _semaphore.WaitAsync(); try { - HttpResponseMessage result = await base.SendAsync(GetPingRequest(request), token); + _lastAlternativeRoutingCheckDateUtc = DateTime.UtcNow; + } + finally + { + _semaphore.Release(); + } + } + + private async Task SendApiPingRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + HttpResponseMessage result = await base.SendAsync(CreateApiPingRequest(request), cancellationToken); return result.IsSuccessStatusCode; } - catch (Exception e) when (e.IsPotentialBlocking()) + catch { return false; } } - private HttpRequestMessage GetPingRequest(HttpRequestMessage request) + private HttpRequestMessage CreateApiPingRequest(HttpRequestMessage request) { HttpRequestMessage pingRequest = new(); UriBuilder uriBuilder = new(request.RequestUri) { - Host = _activeBackendHost, - Path = "tests/ping", + Host = _defaultApiHost, + Path = API_PING_TEST_PATH, Query = string.Empty, }; - pingRequest.Headers.Host = uriBuilder.Host; + pingRequest.Headers.Host = _defaultApiHost; pingRequest.RequestUri = uriBuilder.Uri; pingRequest.Method = HttpMethod.Get; return pingRequest; } - private void ResetBackendHost() + private async Task DisableAlternativeRoutingAsync() + { + await _semaphore.WaitAsync(); + try + { + _lastAlternativeRoutingCheckDateUtc = null; + _activeAlternativeHost = null; + } + finally + { + _semaphore.Release(); + } + } + + private async Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - _appSettings.ActiveAlternativeApiBaseUrl = string.Empty; - _activeBackendHost = _apiHost; + return await base.SendAsync(request, cancellationToken); } - private async Task> TryAlternativeHosts(HttpRequestMessage request, CancellationToken token) + private async Task SendRequestWithAlternativeRoutingAsync( + HttpRequestMessage request, CancellationToken cancellationToken) { - foreach (string host in _appSettings.AlternativeApiBaseUrls) + string alternativeRoutingHost = _alternativeRoutingHostGenerator.Generate(_isUserLoggedIn ? _appSettings.Uid : null); + IList alternativeHosts = await _alternativeHostsManager.GetAsync(alternativeRoutingHost, cancellationToken); + ThrowIfNoAlternativeHostsExist(alternativeHosts); + + HttpResponseMessage httpResponseMessage = null; + string chosenAlternativeHost = null; + foreach (string alternativeHost in alternativeHosts) { try { - _activeBackendHost = host; - HttpResponseMessage result = await SendInternalAsync(request, token); - if (result.IsSuccessStatusCode) - { - _appSettings.ActiveAlternativeApiBaseUrl = host; - } - - return Result.Ok(result); + httpResponseMessage = await SendRequestWithAlternativeHostAsync(alternativeHost, request, cancellationToken); + chosenAlternativeHost = alternativeHost; + await EnableAlternativeRoutingAsync(chosenAlternativeHost); + break; } - catch (Exception ex) when (ex.IsApiCommunicationException()) + catch (Exception ex) { - //Ignore + _logger.Error($"Alternative host '{alternativeHost}' failed.", ex); } } - ResetBackendHost(); - - return Result.Fail(); + ThrowIfAllAlternativeHostsFailed(chosenAlternativeHost); + return httpResponseMessage; } - private async Task SendInternalAsync(HttpRequestMessage request, CancellationToken token) + private void ThrowIfNoAlternativeHostsExist(IList alternativeHosts) { - return await base.SendAsync(GetRequest(request), token); + if (alternativeHosts.IsNullOrEmpty()) + { + _logger.Error(NO_ALTERNATIVE_HOSTS_ERROR_MESSAGE); + throw new AlternativeRoutingException(NO_ALTERNATIVE_HOSTS_ERROR_MESSAGE); + } } - private HttpRequestMessage GetRequest(HttpRequestMessage request) + private void ThrowIfAllAlternativeHostsFailed(string chosenAlternativeHost) { - UriBuilder uriBuilder = new(request.RequestUri) { Host = _activeBackendHost }; - request.Headers.Host = uriBuilder.Host; - request.RequestUri = uriBuilder.Uri; - - return request; + if (chosenAlternativeHost.IsNullOrEmpty()) + { + _logger.Error(ALL_ALTERNATIVE_HOSTS_FAILED_ERROR_MESSAGE); + throw new AlternativeRoutingException(ALL_ALTERNATIVE_HOSTS_FAILED_ERROR_MESSAGE); + } } - private async Task FetchProxies() + private async Task SendRequestWithAlternativeHostAsync(string alternativeHost, + HttpRequestMessage request, CancellationToken cancellationToken) { - _appSettings.LastPrimaryApiFailDateUtc = DateTime.UtcNow; - _appSettings.AlternativeApiBaseUrls = new StringCollection(); - ResetBackendHost(); - - List clients = _dohClients.Get(); - foreach (Client dohClient in clients) + IList alternativeHostIpAddresses = await _dnsManager.GetAsync(alternativeHost, cancellationToken); + ThrowIfAlternativeHostHasNoIpAddresses(alternativeHost, alternativeHostIpAddresses); + foreach (IpAddress alternativeHostIpAddress in alternativeHostIpAddresses) { try { - string host = _mainHostname.Value(_isUserLoggedIn ? _tokenStorage.Uid : string.Empty); - _logger.Info($"Resolving alternative hosts from {host}"); - List alternativeHosts = await dohClient.ResolveTxtAsync(host); - if (alternativeHosts.Count > 0) + HttpResponseMessage httpResponseMessage = await SendRequestToAlternativeHostAsync( + alternativeHostIpAddress, alternativeHost, request, cancellationToken); + if (httpResponseMessage.IsSuccessStatusCode) { - _appSettings.AlternativeApiBaseUrls = GetAlternativeApiBaseUrls(alternativeHosts); - return; + return httpResponseMessage; } } - catch (Exception e) when (e.IsPotentialBlocking() || e.IsApiCommunicationException()) + catch (Exception ex) { - //Ignore + _logger.Error($"Alternative host '{alternativeHost}' with IP address " + + $"'{alternativeHostIpAddress}' failed.", ex); } } + throw new AlternativeRoutingException($"Alternative host '{alternativeHost}' failed."); + } + + private async Task EnableAlternativeRoutingAsync(string alternativeHost) + { + await _semaphore.WaitAsync(); + try + { + _lastAlternativeRoutingCheckDateUtc = DateTime.UtcNow; + _activeAlternativeHost = alternativeHost; + } + finally + { + _semaphore.Release(); + } + } + + private void ThrowIfAlternativeHostHasNoIpAddresses(string alternativeHost, IList alternativeHostIpAddresses) + { + if (alternativeHostIpAddresses.IsNullOrEmpty()) + { + string errorMessage = $"No IP addresses were found for alternative host '{alternativeHost}'."; + _logger.Error(errorMessage); + throw new AlternativeRoutingException(errorMessage); + } } - private StringCollection GetAlternativeApiBaseUrls(List list) + private async Task SendRequestToAlternativeHostAsync(IpAddress ipAddress, + string alternativeHost, HttpRequestMessage request, CancellationToken cancellationToken) { - StringCollection collection = new(); - foreach (string element in list) + string oldUriHost = request.RequestUri.Host; + string oldHeaderHost = request.Headers.Host; + SetRequestHost(request, uriHost: ipAddress.ToString(), headerHost: alternativeHost); + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await SendRequestAsync(request, cancellationToken); + } + finally { - collection.Add(element); + SetRequestHost(request, uriHost: oldUriHost, headerHost: oldHeaderHost); } + return httpResponseMessage; + } - return collection; + private void SetRequestHost(HttpRequestMessage request, string uriHost, string headerHost) + { + UriBuilder uriBuilder = new(request.RequestUri) { Host = uriHost }; + request.Headers.Host = headerHost; + request.RequestUri = uriBuilder.Uri; } public void OnUserLoggedIn() diff --git a/src/Api/ProtonVPN.Api/Handlers/CancellingHandler.cs b/src/Api/ProtonVPN.Api/Handlers/CancellingHandler.cs index ac6ffd824..38217b71f 100644 --- a/src/Api/ProtonVPN.Api/Handlers/CancellingHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/CancellingHandler.cs @@ -33,10 +33,9 @@ public class CancellingHandler : CancellingHandlerBase private readonly Config _config; private readonly CancellationHandle _cancellationHandle = new(); - public CancellingHandler(Config config, UnauthorizedResponseHandler unauthorizedResponseHandler) + public CancellingHandler(Config config) { _config = config; - InnerHandler = unauthorizedResponseHandler; } protected override async Task SendAsync( diff --git a/src/Api/ProtonVPN.Api/Handlers/DnsHandler.cs b/src/Api/ProtonVPN.Api/Handlers/DnsHandler.cs index 1a5093d83..dbd5e1e47 100644 --- a/src/Api/ProtonVPN.Api/Handlers/DnsHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/DnsHandler.cs @@ -18,63 +18,103 @@ */ using System; +using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Caliburn.Micro; -using ProtonVPN.Common.Vpn; -using ProtonVPN.Core.OS.Net.Dns; -using ProtonVPN.Core.Vpn; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.ApiLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Exceptions; namespace ProtonVPN.Api.Handlers { - /// - /// Replaces host with IP in Http requests if network is blocked and VPN is not connected. - /// - /// - /// If an app asks Windows to resolve IP, the DNS requests are send by different process than this app. - /// If VPN is not connected and firewall is blocking system DNS requests, DNS should be resolved by - /// by the app itself to pass through firewall. - /// - public class DnsHandler : DelegatingHandler, IHandle + public class DnsHandler : DelegatingHandler { - private readonly IDnsClient _dnsClient; + private readonly ILogger _logger; + private readonly IDnsManager _dnsManager; - private bool _systemDnsBlocked; + public DnsHandler(ILogger logger, IDnsManager dnsManager) + { + _logger = logger; + _dnsManager = dnsManager; + } - public DnsHandler(IEventAggregator eventAggregator, IDnsClient dnsClient) + protected override async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) { - _dnsClient = dnsClient; + if (request.RequestUri.HostNameType == UriHostNameType.Dns) + { + return await SendRequestToDomainAsync(request, cancellationToken); + } - eventAggregator.Subscribe(this); + return await SendRequestAsync(request, cancellationToken); } - public void Handle(VpnStateChangedEventArgs e) + private async Task SendRequestToDomainAsync(HttpRequestMessage request, + CancellationToken cancellationToken) { - _systemDnsBlocked = e.NetworkBlocked && e.State.Status != VpnStatus.Connected; + IList ipAddresses = await _dnsManager.GetAsync(request.RequestUri.IdnHost, cancellationToken); + if (!ipAddresses.IsNullOrEmpty()) + { + for (int i = 0; i < ipAddresses.Count; i++) + { + try + { + HttpResponseMessage httpResponseMessage = await SendRequestToIpAddressAsync( + ipAddresses[i], request, cancellationToken); + return httpResponseMessage; + } + catch + { + if (i + 1 == ipAddresses.Count) + { + throw; + } + } + } + } + throw new DnsException($"No IP addresses to make the API request to '{request.RequestUri}'."); } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken token) + private async Task SendRequestToIpAddressAsync(IpAddress ipAddress, + HttpRequestMessage request, CancellationToken token) { - if (_systemDnsBlocked) + Uri oldRequestUri = request.RequestUri; + SetRequestHost(request, ipAddress.ToString(), oldRequestUri); + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await SendRequestAsync(request, token); + } + catch (Exception ex) { - request = await ModifyRequest(request, token); + _logger.Error($"API request '{request.RequestUri}' failed.", ex); + ResetRequestUri(request, oldRequestUri); + throw; } + return httpResponseMessage; + } - return await base.SendAsync(request, token); + private void SetRequestHost(HttpRequestMessage request, string uriHost, Uri oldRequestUri) + { + UriBuilder uriBuilder = new(request.RequestUri) { Host = uriHost }; + request.Headers.Host = oldRequestUri.Host; + request.RequestUri = uriBuilder.Uri; } - private async Task ModifyRequest(HttpRequestMessage message, CancellationToken token) + private void ResetRequestUri(HttpRequestMessage request, Uri uri) { - string ip = await _dnsClient.Resolve(message.RequestUri.Host, token); - if (!string.IsNullOrEmpty(ip)) - { - var uriBuilder = new UriBuilder(message.RequestUri) { Host = ip }; - message.Headers.Host = message.RequestUri.Host; - message.RequestUri = uriBuilder.Uri; - } + UriBuilder uriBuilder = new(uri) { Host = uri.Host }; + request.Headers.Host = uriBuilder.Host; + request.RequestUri = uriBuilder.Uri; + } - return message; + private async Task SendRequestAsync(HttpRequestMessage request, CancellationToken token) + { + return await base.SendAsync(request, token); } } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/Handlers/HumanVerificationHandler.cs b/src/Api/ProtonVPN.Api/Handlers/HumanVerificationHandler.cs index 4068a0189..fe835d705 100644 --- a/src/Api/ProtonVPN.Api/Handlers/HumanVerificationHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/HumanVerificationHandler.cs @@ -39,14 +39,11 @@ public class HumanVerificationHandler : HumanVerificationHandlerBase public HumanVerificationHandler(IBaseResponseMessageDeserializer baseResponseDeserializer, IHumanVerifier humanVerifier, - IHumanVerificationConfig humanVerificationConfig, - OutdatedAppHandler outdatedAppHandler) + IHumanVerificationConfig humanVerificationConfig) { _baseResponseDeserializer = baseResponseDeserializer; _humanVerifier = humanVerifier; _enabled = humanVerificationConfig.IsSupported(); - - InnerHandler = outdatedAppHandler; } protected override async Task SendAsync( diff --git a/src/Api/ProtonVPN.Api/Handlers/LoggingHandler.cs b/src/Api/ProtonVPN.Api/Handlers/LoggingHandler.cs index 05bd5cade..4af3942f6 100644 --- a/src/Api/ProtonVPN.Api/Handlers/LoggingHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/LoggingHandler.cs @@ -34,18 +34,16 @@ public class LoggingHandler : LoggingHandlerBase { private readonly ILogger _logger; - public LoggingHandler(SafeDnsHandler safeDnsHandler, ILogger logger) + public LoggingHandler(ILogger logger) { _logger = logger; - - InnerHandler = safeDnsHandler; } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { - string req = $"{request.Method.Method} \"{request.RequestUri.LocalPath}\""; + string req = $"{request.Method.Method} \"{request.RequestUri}\""; try { _logger.Info(req); diff --git a/src/Api/ProtonVPN.Api/Handlers/OutdatedAppHandler.cs b/src/Api/ProtonVPN.Api/Handlers/OutdatedAppHandler.cs index 5d1401cd3..831cb4ac6 100644 --- a/src/Api/ProtonVPN.Api/Handlers/OutdatedAppHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/OutdatedAppHandler.cs @@ -24,7 +24,6 @@ using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Common; using ProtonVPN.Api.Deserializers; -using ProtonVPN.Api.Handlers.Retries; namespace ProtonVPN.Api.Handlers { @@ -36,11 +35,9 @@ public class OutdatedAppHandler : DelegatingHandler private readonly IBaseResponseMessageDeserializer _baseResponseDeserializer; public event EventHandler AppOutdated; - public OutdatedAppHandler(IBaseResponseMessageDeserializer baseResponseDeserializer, - RetryingHandlerBase retryingHandler) + public OutdatedAppHandler(IBaseResponseMessageDeserializer baseResponseDeserializer) { _baseResponseDeserializer = baseResponseDeserializer; - InnerHandler = retryingHandler; } protected override async Task SendAsync(HttpRequestMessage request, diff --git a/src/Api/ProtonVPN.Api/Handlers/Retries/RetryingHandler.cs b/src/Api/ProtonVPN.Api/Handlers/Retries/RetryingHandler.cs index db0f99437..5f8d27f84 100644 --- a/src/Api/ProtonVPN.Api/Handlers/Retries/RetryingHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/Retries/RetryingHandler.cs @@ -28,10 +28,9 @@ public class RetryingHandler : RetryingHandlerBase { private readonly IRetryPolicyProvider _retryPolicyProvider; - public RetryingHandler(LoggingHandlerBase loggingHandler, IRetryPolicyProvider retryPolicyProvider) + public RetryingHandler(IRetryPolicyProvider retryPolicyProvider) { _retryPolicyProvider = retryPolicyProvider; - InnerHandler = loggingHandler; } protected override async Task SendAsync(HttpRequestMessage request, diff --git a/src/Api/ProtonVPN.Api/Handlers/SafeDnsHandler.cs b/src/Api/ProtonVPN.Api/Handlers/SafeDnsHandler.cs deleted file mode 100644 index 0b5d4d5c5..000000000 --- a/src/Api/ProtonVPN.Api/Handlers/SafeDnsHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2022 Proton Technologies AG - * - * This file is part of ProtonVPN. - * - * ProtonVPN 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. - * - * ProtonVPN 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 ProtonVPN. If not, see . - */ - -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Caliburn.Micro; -using DnsClient; -using ProtonVPN.Api.Handlers.TlsPinning; -using ProtonVPN.Core.OS.Net.Dns; - -namespace ProtonVPN.Api.Handlers -{ - /// - /// Suppresses expected exceptions of DnsHandler. - /// - public class SafeDnsHandler : DnsHandler - { - public SafeDnsHandler(CertificateHandler certificateHandler, IEventAggregator eventAggregator, IDnsClient dnsClient) : base(eventAggregator, dnsClient) - { - InnerHandler = certificateHandler; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken token) - { - try - { - return await base.SendAsync(request, token); - } - catch (DnsResponseException) - { - return new FailedDnsResponse(); - } - } - } -} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs b/src/Api/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs new file mode 100644 index 000000000..10bec1b7c --- /dev/null +++ b/src/Api/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; + +namespace ProtonVPN.Api.Handlers.StackBuilders +{ + public class CompleteHttpMessageHandlerStackBuilder + { + private readonly HttpMessageHandler _httpMessageHandler; + private readonly List _delegatingHandlers; + + public CompleteHttpMessageHandlerStackBuilder(HttpMessageHandler httpMessageHandler, + List delegatingHandlers = null) + { + _httpMessageHandler = httpMessageHandler ?? throw new ArgumentNullException(nameof(httpMessageHandler)); + _delegatingHandlers = delegatingHandlers ?? new(); + } + + public HttpMessageHandler Build() + { + List delegatingHandlers = _delegatingHandlers.ToList(); + delegatingHandlers.Reverse(); + HttpMessageHandler innerHttpMessageHandler = _httpMessageHandler; + + foreach (DelegatingHandler delegatingHandler in delegatingHandlers) + { + delegatingHandler.InnerHandler = innerHttpMessageHandler; + innerHttpMessageHandler = delegatingHandler; + } + + return innerHttpMessageHandler; + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs b/src/Api/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs new file mode 100644 index 000000000..189966836 --- /dev/null +++ b/src/Api/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace ProtonVPN.Api.Handlers.StackBuilders +{ + public class HttpMessageHandlerStackBuilder + { + private readonly List _delegatingHandlers = new(); + + public HttpMessageHandlerStackBuilder AddDelegatingHandler(DelegatingHandler delegatingHandler) + { + _delegatingHandlers.Add(delegatingHandler); + return this; + } + + public CompleteHttpMessageHandlerStackBuilder AddLastHandler(HttpMessageHandler httpMessageHandler) + { + if (httpMessageHandler is null) + { + throw new ArgumentNullException(nameof(httpMessageHandler)); + } + if (httpMessageHandler is DelegatingHandler) + { + throw new ArgumentException($"The last handler cannot be of {nameof(DelegatingHandler)} " + + $"type as it would not have an InnerHandler defined."); + } + return new CompleteHttpMessageHandlerStackBuilder(httpMessageHandler, _delegatingHandlers); + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/Handlers/TlsPinning/ReportClient.cs b/src/Api/ProtonVPN.Api/Handlers/TlsPinning/ReportClient.cs index 8549cedec..0ac55f81c 100644 --- a/src/Api/ProtonVPN.Api/Handlers/TlsPinning/ReportClient.cs +++ b/src/Api/ProtonVPN.Api/Handlers/TlsPinning/ReportClient.cs @@ -46,9 +46,8 @@ public void Send(ReportBody body) { _httpClient.PostAsync(_uri, content); } - catch (HttpRequestException) + catch { - //ignore } }); } diff --git a/src/Api/ProtonVPN.Api/Handlers/UnauthorizedResponseHandler.cs b/src/Api/ProtonVPN.Api/Handlers/UnauthorizedResponseHandler.cs index 7a2ee3671..d71ceb482 100644 --- a/src/Api/ProtonVPN.Api/Handlers/UnauthorizedResponseHandler.cs +++ b/src/Api/ProtonVPN.Api/Handlers/UnauthorizedResponseHandler.cs @@ -29,7 +29,6 @@ using ProtonVPN.Common.Logging.Categorization.Events.UserLogs; using ProtonVPN.Common.OS.Net.Http; using ProtonVPN.Common.Threading; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Settings; namespace ProtonVPN.Api.Handlers @@ -41,7 +40,7 @@ namespace ProtonVPN.Api.Handlers public class UnauthorizedResponseHandler : DelegatingHandler { private readonly ITokenClient _tokenClient; - private readonly ITokenStorage _tokenStorage; + private readonly IAppSettings _appSettings; private readonly IUserStorage _userStorage; private readonly ILogger _logger; @@ -50,18 +49,15 @@ public class UnauthorizedResponseHandler : DelegatingHandler public event EventHandler SessionExpired; public UnauthorizedResponseHandler( - HumanVerificationHandlerBase humanVerificationHandler, ITokenClient tokenClient, - ITokenStorage tokenStorage, + IAppSettings appSettings, IUserStorage userStorage, ILogger logger) { _tokenClient = tokenClient; - _tokenStorage = tokenStorage; + _appSettings = appSettings; _userStorage = userStorage; _logger = logger; - - InnerHandler = humanVerificationHandler; } protected override async Task SendAsync(HttpRequestMessage request, @@ -81,7 +77,7 @@ protected override async Task SendAsync(HttpRequestMessage } HttpResponseMessage response = await base.SendAsync(request, cancellationToken); - if (response.StatusCode == HttpStatusCode.Unauthorized && !_userStorage.User().Empty()) + if (response.StatusCode == HttpStatusCode.Unauthorized && !_userStorage.GetUser().Empty()) { try { @@ -134,7 +130,7 @@ private async Task Refresh(Task refreshT private async Task RefreshTokens(CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(_tokenStorage.RefreshToken) || string.IsNullOrEmpty(_tokenStorage.Uid)) + if (string.IsNullOrEmpty(_appSettings.RefreshToken) || string.IsNullOrEmpty(_appSettings.Uid)) { return RefreshTokenStatus.Unauthorized; } @@ -146,8 +142,8 @@ private async Task RefreshTokens(CancellationToken cancellat if (response.Success) { - _tokenStorage.AccessToken = response.Value.AccessToken; - _tokenStorage.RefreshToken = response.Value.RefreshToken; + _appSettings.AccessToken = response.Value.AccessToken; + _appSettings.RefreshToken = response.Value.RefreshToken; return RefreshTokenStatus.Success; } @@ -156,7 +152,7 @@ private async Task RefreshTokens(CancellationToken cancellat { _logger.Error($"An error occurred when refreshing the auth token: {e.ParamName}"); } - catch (HttpRequestException) + catch (Exception) { return RefreshTokenStatus.Fail; } @@ -166,7 +162,7 @@ private async Task RefreshTokens(CancellationToken cancellat private void PrepareRequest(HttpRequestMessage request) { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenStorage.AccessToken); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _appSettings.AccessToken); } } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/HttpClientFactory.cs b/src/Api/ProtonVPN.Api/HttpClientFactory.cs deleted file mode 100644 index 4c824bfd6..000000000 --- a/src/Api/ProtonVPN.Api/HttpClientFactory.cs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2022 Proton Technologies AG - * - * This file is part of ProtonVPN. - * - * ProtonVPN 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. - * - * ProtonVPN 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 ProtonVPN. If not, see . - */ - -using System; -using System.Net.Http; -using ProtonVPN.Api.Handlers; -using ProtonVPN.Common.Configuration; - -namespace ProtonVPN.Api -{ - public class HttpClientFactory : IHttpClientFactory - { - private readonly Config _config; - private readonly AlternativeHostHandler _alternativeHostHandler; - - public HttpClientFactory(Config config, AlternativeHostHandler alternativeHostHandler) - { - _config = config; - _alternativeHostHandler = alternativeHostHandler; - } - - public HttpClient GetApiHttpClientWithoutCache() - { - HttpClient client = GetApiHttpClientWithCache(); - client.DefaultRequestHeaders.ConnectionClose = true; - return client; - } - - public HttpClient GetApiHttpClientWithCache() - { - return new(_alternativeHostHandler) { BaseAddress = new Uri(_config.Urls.ApiUrl) }; - } - } -} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/IApiHttpClientFactory.cs b/src/Api/ProtonVPN.Api/IApiHttpClientFactory.cs new file mode 100644 index 000000000..7c2b54174 --- /dev/null +++ b/src/Api/ProtonVPN.Api/IApiHttpClientFactory.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Net.Http; + +namespace ProtonVPN.Api +{ + public interface IApiHttpClientFactory + { + HttpClient GetApiHttpClientWithCache(); + HttpClient GetApiHttpClientWithoutCache(); + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Core/Abstract/ITokenStorage.cs b/src/Api/ProtonVPN.Api/ITokenHttpClientFactory.cs similarity index 80% rename from src/ProtonVPN.Core/Abstract/ITokenStorage.cs rename to src/Api/ProtonVPN.Api/ITokenHttpClientFactory.cs index 8f5c4647c..11aef20cf 100644 --- a/src/ProtonVPN.Core/Abstract/ITokenStorage.cs +++ b/src/Api/ProtonVPN.Api/ITokenHttpClientFactory.cs @@ -17,14 +17,12 @@ * along with ProtonVPN. If not, see . */ -namespace ProtonVPN.Core.Abstract +using System.Net.Http; + +namespace ProtonVPN.Api { - public interface ITokenStorage + public interface ITokenHttpClientFactory { - string AccessToken { get; set; } - - string RefreshToken { get; set; } - - string Uid { get; set; } + HttpClient GetTokenHttpClient(); } } \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/ProtonVPN.Api.csproj b/src/Api/ProtonVPN.Api/ProtonVPN.Api.csproj index 677be29f5..681fe4889 100644 --- a/src/Api/ProtonVPN.Api/ProtonVPN.Api.csproj +++ b/src/Api/ProtonVPN.Api/ProtonVPN.Api.csproj @@ -36,42 +36,12 @@ true - - ..\..\packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll - - - ..\..\packages\Caliburn.Micro.Core.3.2.0\lib\net45\Caliburn.Micro.dll - - - ..\..\packages\DnsClient.1.5.0\lib\net471\DnsClient.dll - - - ..\..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\Polly.7.2.0\lib\net472\Polly.dll - - - ..\..\packages\Polly.Contrib.WaitAndRetry.1.1.0\lib\net472\Polly.Contrib.WaitAndRetry.dll - - - ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - ..\..\packages\System.Security.AccessControl.5.0.0\lib\net461\System.Security.AccessControl.dll - - - ..\..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll - @@ -96,6 +66,9 @@ + + + @@ -103,7 +76,6 @@ - @@ -118,7 +90,6 @@ - @@ -128,14 +99,20 @@ - + - + + + + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} ProtonVPN.Api.Contracts @@ -155,8 +132,29 @@ - - + + + 1.8.9 + + + 3.2.0 + + + 1.5.0 + + + 13.0.1 + + + 7.2.0 + + + 1.1.0 + + + 4.3.0 + + \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/TokenClient.cs b/src/Api/ProtonVPN.Api/TokenClient.cs index 53f5acb03..c95ac51a7 100644 --- a/src/Api/ProtonVPN.Api/TokenClient.cs +++ b/src/Api/ProtonVPN.Api/TokenClient.cs @@ -25,25 +25,27 @@ using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Auth; using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Logging; -using ProtonVPN.Core.Abstract; +using ProtonVPN.Common.Logging.Categorization.Events.ApiLogs; using ProtonVPN.Core.Settings; namespace ProtonVPN.Api { public class TokenClient : BaseApiClient, ITokenClient { + private const int REFRESH_TOKEN_LOG_LENGTH = 5; private readonly HttpClient _client; public TokenClient( ILogger logger, - HttpClient client, + ITokenHttpClientFactory tokenHttpClientFactory, IApiAppVersion appVersion, - ITokenStorage tokenStorage, + IAppSettings appSettings, IAppLanguageCache appLanguageCache, - Config config) : base(logger, appVersion, tokenStorage, appLanguageCache, config) + Config config) : base(logger, appVersion, appSettings, appLanguageCache, config) { - _client = client; + _client = tokenHttpClientFactory.GetTokenHttpClient(); } public async Task> RefreshTokenAsync(CancellationToken token) @@ -51,7 +53,7 @@ public async Task> RefreshTokenAsync(Can RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest { ResponseType = "token", - RefreshToken = TokenStorage.RefreshToken, + RefreshToken = AppSettings.RefreshToken, GrantType = "refresh_token", RedirectUri = "http://api.protonvpn.ch" }; @@ -62,16 +64,28 @@ public async Task> RefreshTokenAsync(Can HttpRequestMessage request = GetAuthorizedRequest(HttpMethod.Post, "auth/refresh"); ValidateRequestHeaders(request); request.Content = GetJsonContent(refreshTokenRequest); + LogRefreshToken(); using HttpResponseMessage response = await _client.SendAsync(request, token); return await GetApiResponseResult(response); } - catch (Exception e) when (e.IsApiCommunicationException()) + catch (Exception e) { + if (!e.IsApiCommunicationException()) + { + Logger.Error("An exception occurred in an API request " + + "that is not related with its communication.", e); + } throw new HttpRequestException(e.Message); } } + private void LogRefreshToken() + { + Logger.Info($"Using refresh token value (showing only first {REFRESH_TOKEN_LOG_LENGTH} chars) " + + $"{AppSettings.RefreshToken.GetFirstChars(REFRESH_TOKEN_LOG_LENGTH)}"); + } + private void ValidateRefreshTokenData(RefreshTokenRequest request) { if (request.RefreshToken == null) diff --git a/src/Api/ProtonVPN.Api/TokenHttpClientFactory.cs b/src/Api/ProtonVPN.Api/TokenHttpClientFactory.cs new file mode 100644 index 000000000..ddb72ff64 --- /dev/null +++ b/src/Api/ProtonVPN.Api/TokenHttpClientFactory.cs @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Net.Http; +using ProtonVPN.Api.Handlers; +using ProtonVPN.Api.Handlers.Retries; +using ProtonVPN.Api.Handlers.StackBuilders; +using ProtonVPN.Api.Handlers.TlsPinning; +using ProtonVPN.Common.Configuration; + +namespace ProtonVPN.Api +{ + public class TokenHttpClientFactory : ITokenHttpClientFactory + { + private readonly IConfiguration _configuration; + private readonly HttpMessageHandler _innerHandler; + + public TokenHttpClientFactory(IConfiguration configuration, + AlternativeHostHandler alternativeHostHandler, + RetryingHandler retryingHandler, + DnsHandler dnsHandler, + LoggingHandlerBase loggingHandlerBase, + CertificateHandler certificateHandler) + { + _configuration = configuration; + + _innerHandler = new HttpMessageHandlerStackBuilder() + .AddDelegatingHandler(alternativeHostHandler) + .AddDelegatingHandler(retryingHandler) + .AddDelegatingHandler(dnsHandler) + .AddDelegatingHandler(loggingHandlerBase) + .AddLastHandler(certificateHandler) + .Build(); + } + + public HttpClient GetTokenHttpClient() + { + return new HttpClient(_innerHandler) { BaseAddress = new Uri(_configuration.Urls.ApiUrl) }; + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/app.config b/src/Api/ProtonVPN.Api/app.config index d29e3de6e..ba0ac79da 100644 --- a/src/Api/ProtonVPN.Api/app.config +++ b/src/Api/ProtonVPN.Api/app.config @@ -2,6 +2,10 @@ + + + + diff --git a/src/Api/ProtonVPN.Api/packages.config b/src/Api/ProtonVPN.Api/packages.config deleted file mode 100644 index 778e54e6e..000000000 --- a/src/Api/ProtonVPN.Api/packages.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/AlternativeRouting/IAlternativeRoutingHostGenerator.cs b/src/Dns/ProtonVPN.Dns.Contracts/AlternativeRouting/IAlternativeRoutingHostGenerator.cs new file mode 100644 index 000000000..4f1d7d800 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/AlternativeRouting/IAlternativeRoutingHostGenerator.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +namespace ProtonVPN.Dns.Contracts.AlternativeRouting +{ + public interface IAlternativeRoutingHostGenerator + { + string Generate(string uid); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/DnsResponse.cs b/src/Dns/ProtonVPN.Dns.Contracts/DnsResponse.cs new file mode 100644 index 000000000..27f7786e3 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/DnsResponse.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using ProtonVPN.Common.Networking; + +namespace ProtonVPN.Dns.Contracts +{ + public class DnsResponse + { + public string Host { get; set; } + public IList IpAddresses { get; set; } + public IList AlternativeHosts { get; set; } + + public TimeSpan TimeToLive { get; set; } + public DateTime ResponseDateTimeUtc { get; set; } + public DateTime ExpirationDateTimeUtc { get; set; } + + public DnsResponse() + { + } + + public DnsResponse(string host, TimeSpan timeToLive, IList ipAddresses, DateTime? responseDateTimeUtc = null) + : this(host, timeToLive, ipAddresses, null, responseDateTimeUtc) + { + } + + public DnsResponse(string host, TimeSpan timeToLive, IList alternativeHosts, DateTime? responseDateTimeUtc = null) + : this(host, timeToLive, null, alternativeHosts, responseDateTimeUtc) + { + } + + private DnsResponse(string host, TimeSpan timeToLive, IList ipAddresses, + IList alternativeHosts, DateTime? responseDateTimeUtc) + { + ResponseDateTimeUtc = responseDateTimeUtc ?? DateTime.UtcNow; + ExpirationDateTimeUtc = ResponseDateTimeUtc + timeToLive; + TimeToLive = timeToLive; + + Host = host; + IpAddresses = ipAddresses ?? new List(); + AlternativeHosts = alternativeHosts?.Distinct().ToList() ?? new List(); + } + + public void SetDatesAndTimeToLive(TimeSpan timeToLive) + { + ResponseDateTimeUtc = DateTime.UtcNow; + ExpirationDateTimeUtc = ResponseDateTimeUtc + timeToLive; + TimeToLive = timeToLive; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/Exceptions/DnsException.cs b/src/Dns/ProtonVPN.Dns.Contracts/Exceptions/DnsException.cs new file mode 100644 index 000000000..25a0f7912 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/Exceptions/DnsException.cs @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; + +namespace ProtonVPN.Dns.Contracts.Exceptions +{ + public class DnsException : Exception + { + public DnsException() + { + } + + public DnsException(string message) : base(message) + { + } + + public DnsException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/IAlternativeHostsManager.cs b/src/Dns/ProtonVPN.Dns.Contracts/IAlternativeHostsManager.cs new file mode 100644 index 000000000..6accc7dda --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/IAlternativeHostsManager.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ProtonVPN.Dns.Contracts +{ + public interface IAlternativeHostsManager + { + /// Returns the cached alternative hosts if they are fresh, otherwise makes a HTTPS DNS request + /// for TXT records. If the resolve fails, returns the cached alternative hosts. + Task> GetAsync(string host, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/IDnsManager.cs b/src/Dns/ProtonVPN.Dns.Contracts/IDnsManager.cs new file mode 100644 index 000000000..917de8260 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/IDnsManager.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Networking; + +namespace ProtonVPN.Dns.Contracts +{ + public interface IDnsManager + { + /// Returns the cached IP addresses if they are fresh, otherwise resolves for new ones. + /// If the resolve fails, returns the cached IP addresses. + Task> GetAsync(string host, CancellationToken cancellationToken); + + /// Resolves for IP addresses. + /// Doesn't check cache and doesn't return cached IP addresses in case of failure. + Task> ResolveWithoutCacheAsync(string host, CancellationToken cancellationToken); + + /// Returns the cached IP addresses. + IList GetFromCache(string host); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/IDnsOverHttpsProvidersManager.cs b/src/Dns/ProtonVPN.Dns.Contracts/IDnsOverHttpsProvidersManager.cs new file mode 100644 index 000000000..702ab50dd --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/IDnsOverHttpsProvidersManager.cs @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Networking; + +namespace ProtonVPN.Dns.Contracts +{ + public interface IDnsOverHttpsProvidersManager + { + /// Returns the cached IP addresses of the DNS over HTTPS providers if they are fresh, otherwise + /// makes a UDP DNS request for them. If the resolve fails, returns the cached IP addresses. + Task> GetAsync(string host, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/NameServers/INameServersLoader.cs b/src/Dns/ProtonVPN.Dns.Contracts/NameServers/INameServersLoader.cs new file mode 100644 index 000000000..8048b9513 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/NameServers/INameServersLoader.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Net; + +namespace ProtonVPN.Dns.Contracts.NameServers +{ + public interface INameServersLoader + { + IList Get(); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/ProtonVPN.Dns.Contracts.csproj b/src/Dns/ProtonVPN.Dns.Contracts/ProtonVPN.Dns.Contracts.csproj new file mode 100644 index 000000000..fe10e2bf0 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/ProtonVPN.Dns.Contracts.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + Library + Properties + ProtonVPN.Dns.Contracts + ProtonVPN.Dns.Contracts + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + + + + + \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsResolver.cs b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsResolver.cs new file mode 100644 index 000000000..c39ee88b5 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsResolver.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +namespace ProtonVPN.Dns.Contracts.Resolvers +{ + public interface IDnsOverHttpsResolver : IDnsResolver + { + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsTxtRecordsResolver.cs b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsTxtRecordsResolver.cs new file mode 100644 index 000000000..395b99f2a --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverHttpsTxtRecordsResolver.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +namespace ProtonVPN.Dns.Contracts.Resolvers +{ + public interface IDnsOverHttpsTxtRecordsResolver : IDnsResolver + { + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverUdpResolver.cs b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverUdpResolver.cs new file mode 100644 index 000000000..5f82e25b3 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsOverUdpResolver.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +namespace ProtonVPN.Dns.Contracts.Resolvers +{ + public interface IDnsOverUdpResolver : IDnsResolver + { + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsResolver.cs b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsResolver.cs new file mode 100644 index 000000000..37147db28 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Contracts/Resolvers/IDnsResolver.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Threading; +using System.Threading.Tasks; + +namespace ProtonVPN.Dns.Contracts.Resolvers +{ + public interface IDnsResolver + { + Task ResolveAsync(string host, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/test/ProtonVPN.Service.Contract.Test/app.config b/src/Dns/ProtonVPN.Dns.Contracts/app.config similarity index 100% rename from test/ProtonVPN.Service.Contract.Test/app.config rename to src/Dns/ProtonVPN.Dns.Contracts/app.config diff --git a/src/Dns/ProtonVPN.Dns.Installers/DnsModule.cs b/src/Dns/ProtonVPN.Dns.Installers/DnsModule.cs new file mode 100644 index 000000000..89704585d --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Installers/DnsModule.cs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using Autofac; +using ProtonVPN.Dns.AlternativeRouting; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.HttpClients; +using ProtonVPN.Dns.NameServers; +using ProtonVPN.Dns.Resolvers; + +namespace ProtonVPN.Dns.Installers +{ + public class DnsModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Installers/ProtonVPN.Dns.Installers.csproj b/src/Dns/ProtonVPN.Dns.Installers/ProtonVPN.Dns.Installers.csproj new file mode 100644 index 000000000..52d4fa609 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Installers/ProtonVPN.Dns.Installers.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {E52E34FD-40AE-425B-9C1A-2CAB3EE2704F} + Library + Properties + ProtonVPN.Dns.Installers + ProtonVPN.Dns.Installers + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + + + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75} + ProtonVPN.Dns + + + + + 4.9.4 + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Vpn.Test/app.config b/src/Dns/ProtonVPN.Dns.Installers/app.config similarity index 63% rename from test/ProtonVPN.Vpn.Test/app.config rename to src/Dns/ProtonVPN.Dns.Installers/app.config index 3df9e48cd..f10b4ac05 100644 --- a/test/ProtonVPN.Vpn.Test/app.config +++ b/src/Dns/ProtonVPN.Dns.Installers/app.config @@ -6,10 +6,6 @@ - - - - \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/AlternativeHostsManagerTest.cs b/src/Dns/ProtonVPN.Dns.Tests/AlternativeHostsManagerTest.cs new file mode 100644 index 000000000..fb73f5215 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/AlternativeHostsManagerTest.cs @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using NSubstitute.Core; +using NSubstitute.ExceptionExtensions; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests +{ + [TestClass] + public class AlternativeHostsManagerTest + { + private const string HOST = "dMFYGSLTQOJXXI33OOZYG4LTDNA.protonpro.xyz"; + private const string DIFFERENT_HOST = "api.protonvpn.ch"; + private static readonly TimeSpan FAILED_DNS_REQUEST_TIMEOUT = TimeSpan.FromSeconds(5); + private static readonly TimeSpan NEW_TTL_ON_RESOLVE_ERROR = TimeSpan.FromMinutes(10); + + private MockOfLogger _logger; + private CancellationTokenSource _cancellationTokenSource; + private IDnsOverHttpsTxtRecordsResolver _dnsOverHttpsTxtRecordsResolver; + private IAppSettings _appSettings; + private IConfiguration _configuration; + private IDnsCacheManager _dnsCacheManager; + private AlternativeHostsManager _alternativeHostsManager; + + [TestInitialize] + public void TestInitialize() + { + _logger = new MockOfLogger(); + _cancellationTokenSource = new CancellationTokenSource(); + _dnsOverHttpsTxtRecordsResolver = Substitute.For(); + _appSettings = Substitute.For(); + _configuration = Substitute.For(); + _configuration.FailedDnsRequestTimeout.Returns(FAILED_DNS_REQUEST_TIMEOUT); + _configuration.NewCacheTimeToLiveOnResolveError.Returns(NEW_TTL_ON_RESOLVE_ERROR); + _dnsCacheManager = new MockOfDnsCacheManager(_appSettings); + _alternativeHostsManager = new AlternativeHostsManager(_dnsOverHttpsTxtRecordsResolver, + _appSettings, _configuration, _logger, _dnsCacheManager); + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _cancellationTokenSource = null; + _dnsOverHttpsTxtRecordsResolver = null; + _appSettings = null; + _configuration = null; + _dnsCacheManager = null; + _alternativeHostsManager = null; + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndAllFails() + { + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public async Task TestGetAsync_WhenSpecificHostIsNotCachedAndAllFails() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(DIFFERENT_HOST)); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + private DnsResponse CreateDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(12), GetAlternativeHosts()); + } + + private IList GetAlternativeHosts() + { + return new List() + { + "protonvpn.com", + "proton.me", + "protonstatus.com" + }; + } + + private ConcurrentDictionary CreateDnsCache(params DnsResponse[] dnsResponses) + { + ConcurrentDictionary dictionary = new(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + dictionary.TryAdd(dnsResponse.Host, dnsResponse); + } + return dictionary; + } + + [TestMethod] + public async Task TestGetAsync_WhenHasFreshCache() + { + InitializeDnsOverHttpsTxtRecordsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertResultEqualsCache(result); + Assert.AreEqual(0, _logger.Logs.Count); + await AssertNoResolverWasCalledAsync(); + } + + private void AssertResultEqualsCache(IList result) + { + Assert.AreEqual(_appSettings.DnsCache[HOST].AlternativeHosts.Count, result.Count); + foreach (string resultAlternativeHost in result) + { + Assert.IsTrue(_appSettings.DnsCache[HOST].AlternativeHosts.Contains(resultAlternativeHost)); + } + } + + private void InitializeDnsOverHttpsTxtRecordsResolver() + { + _dnsOverHttpsTxtRecordsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(CreateDnsResolverResponse); + } + + private DnsResponse CreateDnsResolverResponse(CallInfo arg) + { + string host = arg.ArgAt(0); + return CreateDnsResponse(host); + } + + private async Task AssertNoResolverWasCalledAsync() + { + await _dnsOverHttpsTxtRecordsResolver.Received(0).ResolveAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsTxtRecordsResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + + private async Task AssertCalledResolverOnceAsync() + { + await _dnsOverHttpsTxtRecordsResolver.Received(1).ResolveAsync(Arg.Any(), Arg.Any()); + await _dnsOverHttpsTxtRecordsResolver.Received(1).ResolveAsync(HOST, _cancellationTokenSource.Token); + } + + private void AssertCacheAfterSuccessfulResolve(IList result, DateTime testStartDateTimeUtc) + { + AssertResultEqualsCache(result); + Assert.AreEqual(TimeSpan.FromMinutes(12), _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsTxtRecordsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + + private DnsResponse CreateExpiredDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(15), GetWrongAlternativeHosts(), DateTime.UtcNow.AddMinutes(-16)); + } + + private IList GetWrongAlternativeHosts() + { + return new List() + { + "protonvpn.com", + "proton.me", + "protonstatus.com" + }; + } + + private void AssertCacheBeforeExecution() + { + AssertCacheAlternativeHosts(); + Assert.AreEqual(TimeSpan.FromMinutes(15), _appSettings.DnsCache[HOST].TimeToLive); + } + + private void AssertCacheAlternativeHosts() + { + IList wrongAlternativeHosts = GetWrongAlternativeHosts(); + Assert.AreEqual(wrongAlternativeHosts.Count, _appSettings.DnsCache[HOST].AlternativeHosts.Count); + foreach (string wrongAlternativeHost in wrongAlternativeHosts) + { + Assert.IsTrue(_appSettings.DnsCache[HOST].AlternativeHosts.Contains(wrongAlternativeHost)); + } + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolvesFail() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertResultEqualsCache(result); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + + private void AssertCacheAfterFailedResolve(DateTime testStartDateTimeUtc) + { + AssertCacheAlternativeHosts(); + Assert.AreEqual(NEW_TTL_ON_RESOLVE_ERROR, _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndUdpResolveThrows() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverHttpsTxtRecordsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertResultEqualsCache(result); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndUdpResolveThrows() + { + _dnsOverHttpsTxtRecordsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledResolverOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasNothingIsCached() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsTxtRecordsResolver(); + + IList result1 = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + + IList result2 = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasExpiredCache() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsTxtRecordsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result1 = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + + IList result2 = await _alternativeHostsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertCalledResolverOnceAsync(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/AlternativeRouting/AlternativeRoutingHostGeneratorTest.cs b/src/Dns/ProtonVPN.Dns.Tests/AlternativeRouting/AlternativeRoutingHostGeneratorTest.cs new file mode 100644 index 000000000..3c8042e7e --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/AlternativeRouting/AlternativeRoutingHostGeneratorTest.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Dns.AlternativeRouting; + +namespace ProtonVPN.Dns.Tests.AlternativeRouting +{ + [TestClass] + public class AlternativeRoutingHostGeneratorTest + { + private const string API_URL = "https://api.protonvpn.ch"; + private const string EXPECTED_BASE_HOST = "dMFYGSLTQOJXXI33OOZYG4LTDNA.protonpro.xyz"; + + [TestMethod] + [DataRow(null, EXPECTED_BASE_HOST)] + [DataRow("", EXPECTED_BASE_HOST)] + [DataRow("abc123", $"abc123.{EXPECTED_BASE_HOST}")] + public void TestGenerate(string uid, string expectedResult) + { + IConfiguration configuration = Substitute.For(); + configuration.Urls.Returns(new UrlConfig() + { + ApiUrl = API_URL + }); + AlternativeRoutingHostGenerator generator = new(configuration); + + string result = generator.Generate(uid); + + Assert.AreEqual(expectedResult, result); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Caching/DnsCacheManagerTest.cs b/src/Dns/ProtonVPN.Dns.Tests/Caching/DnsCacheManagerTest.cs new file mode 100644 index 000000000..822e82fa0 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Caching/DnsCacheManagerTest.cs @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests.Caching +{ + [TestClass] + public class DnsCacheManagerTest + { + public const int NUM_OF_PARALLEL_OPERATIONS = 100; + + private IAppSettings _appSettings; + private MockOfLogger _logger; + private DnsCacheManager _dnsCacheManager; + + [TestInitialize] + public void TestInitialize() + { + _appSettings = Substitute.For(); + _logger = new MockOfLogger(); + _dnsCacheManager = new DnsCacheManager(_appSettings, _logger); + } + + [TestCleanup] + public void TestCleanup() + { + _appSettings = null; + _logger = null; + _dnsCacheManager = null; + } + + [TestMethod] + public async Task TestAddOrReplaceAsync_WhenCacheIsNull() + { + DnsResponse dnsResponse = new("host", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + Assert.IsNull(_appSettings.DnsCache); + + await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + } + + [TestMethod] + public async Task TestAddOrReplaceAsync_WhenCacheContainsSameHost() + { + DnsResponse dnsResponse = new("host", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + + DnsResponse cachedDnsResponse = new("host", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + } + + [TestMethod] + public async Task TestAddOrReplaceAsync_WhenCacheContainsDifferentHost() + { + DnsResponse dnsResponse = new("host12", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + + DnsResponse cachedDnsResponse = new("host13", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(cachedDnsResponse.Host)); + Assert.AreEqual(cachedDnsResponse, _appSettings.DnsCache[cachedDnsResponse.Host]); + } + + [TestMethod] + public async Task TestAddOrReplaceAsync_WhenArgumentsAndCacheAreNull() + { + Assert.IsNull(_appSettings.DnsCache); + + await _dnsCacheManager.AddOrReplaceAsync(null, null); + + Assert.IsNull(_appSettings.DnsCache); + } + + [TestMethod] + public async Task TestAddOrReplaceAsync_WhenArgumentsAreNullAndCacheContainsDifferentHost() + { + DnsResponse cachedDnsResponse = new("host13", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.AddOrReplaceAsync(null, null); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(cachedDnsResponse.Host)); + Assert.AreEqual(cachedDnsResponse, _appSettings.DnsCache[cachedDnsResponse.Host]); + } + + [TestMethod] + public void TestAddOrReplaceAsync_ParallelWithDifferentHosts() + { + IList dnsResponses = new List(); + for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) + { + dnsResponses.Add(new DnsResponse($"host{i}", + TimeSpan.FromSeconds(100 + i), + new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); + } + Assert.IsNull(_appSettings.DnsCache); + + IList> tasks = new List>(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + tasks.Add(_dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse)); + } + + Task.WaitAll(tasks.ToArray()); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.AreEqual(NUM_OF_PARALLEL_OPERATIONS, _appSettings.DnsCache.Count); + foreach (DnsResponse dnsResponse in dnsResponses) + { + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + } + } + + [TestMethod] + public void TestAddOrReplaceAsync_ParallelWithSameHost() + { + IList dnsResponses = new List(); + for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) + { + dnsResponses.Add(new DnsResponse("host", + TimeSpan.FromSeconds(100 + i), + new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); + } + Assert.IsNull(_appSettings.DnsCache); + + IList> tasks = new List>(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + tasks.Add(_dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse)); + } + + Task.WaitAll(tasks.ToArray()); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.AreEqual(1, _appSettings.DnsCache.Count); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey("host")); + Assert.AreEqual("host", _appSettings.DnsCache["host"].Host); + Assert.IsTrue(_appSettings.DnsCache.Values.Single().IpAddresses.Single().ToString().StartsWith("192.168.")); + } + + [TestMethod] + public async Task TestUpdateAsync_WhenCacheIsNull() + { + DnsResponse dnsResponse = new("host", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + Assert.IsNull(_appSettings.DnsCache); + + await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); + + Assert.IsNull(_appSettings.DnsCache); + } + + [TestMethod] + public async Task TestUpdateAsync_WhenCacheContainsDifferentHost() + { + DnsResponse dnsResponse = new("host12", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + + DnsResponse cachedDnsResponse = new("host13", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(cachedDnsResponse.Host)); + Assert.AreEqual(cachedDnsResponse, _appSettings.DnsCache[cachedDnsResponse.Host]); + Assert.IsFalse(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + } + + [TestMethod] + public async Task TestUpdateAsync_WhenCacheContainsSameHost() + { + DnsResponse dnsResponse = new("host", + TimeSpan.FromSeconds(12), + new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); + + DnsResponse cachedDnsResponse = new("host", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + } + + [TestMethod] + public async Task TestUpdateAsync_WhenArgumentsAndCacheAreNull() + { + Assert.IsNull(_appSettings.DnsCache); + + await _dnsCacheManager.UpdateAsync(null, null); + + Assert.IsNull(_appSettings.DnsCache); + } + + [TestMethod] + public async Task TestUpdateAsync_WhenArgumentsAreNullAndCacheContainsDifferentHost() + { + DnsResponse cachedDnsResponse = new("host13", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + await _dnsCacheManager.UpdateAsync(null, null); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(cachedDnsResponse.Host)); + Assert.AreEqual(cachedDnsResponse, _appSettings.DnsCache[cachedDnsResponse.Host]); + } + + [TestMethod] + public void TestUpdateAsync_ParallelWithDifferentHosts() + { + _appSettings.DnsCache = new(); + IList dnsResponses = new List(); + for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) + { + DnsResponse cachedDnsResponse = new($"host{i}", + TimeSpan.FromSeconds(1000 + i), + new List { new IpAddress(IPAddress.Parse($"172.16.{i}.{i}")) }); + _appSettings.DnsCache.TryAdd(cachedDnsResponse.Host, cachedDnsResponse); + + DnsResponse dnsResponse = new(cachedDnsResponse.Host, + TimeSpan.FromSeconds(100 + i), + new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) }); + dnsResponses.Add(dnsResponse); + } + Assert.AreEqual(NUM_OF_PARALLEL_OPERATIONS, _appSettings.DnsCache.Count); + foreach (DnsResponse cachedDnsResponse in _appSettings.DnsCache.Values) + { + DnsResponse newDnsResponse = dnsResponses.Single(dr => dr.Host == cachedDnsResponse.Host); + Assert.AreNotEqual(cachedDnsResponse, newDnsResponse); + Assert.AreNotEqual(cachedDnsResponse.TimeToLive, newDnsResponse.TimeToLive); + Assert.AreNotEqual(cachedDnsResponse.IpAddresses, newDnsResponse.IpAddresses); + } + + IList> tasks = new List>(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + tasks.Add(_dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse)); + } + + Task.WaitAll(tasks.ToArray()); + + Assert.AreEqual(NUM_OF_PARALLEL_OPERATIONS, _appSettings.DnsCache.Count); + foreach (DnsResponse dnsResponse in dnsResponses) + { + Assert.IsTrue(_appSettings.DnsCache.ContainsKey(dnsResponse.Host)); + Assert.AreEqual(dnsResponse, _appSettings.DnsCache[dnsResponse.Host]); + } + } + + [TestMethod] + public void TestUpdateAsync_ParallelWithSameHost() + { + DnsResponse cachedDnsResponse = new("host", + TimeSpan.FromSeconds(13), + new List { new IpAddress(IPAddress.Parse("172.16.1.1")) }); + _appSettings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; + + IList dnsResponses = new List(); + for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) + { + dnsResponses.Add(new DnsResponse("host", + TimeSpan.FromSeconds(100 + i), + new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); + } + Assert.IsNotNull(_appSettings.DnsCache); + Assert.AreEqual(1, _appSettings.DnsCache.Count); + Assert.AreEqual(cachedDnsResponse, _appSettings.DnsCache.Values.Single()); + + IList> tasks = new List>(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + tasks.Add(_dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse)); + } + + Task.WaitAll(tasks.ToArray()); + + Assert.IsNotNull(_appSettings.DnsCache); + Assert.AreEqual(1, _appSettings.DnsCache.Count); + Assert.IsTrue(_appSettings.DnsCache.ContainsKey("host")); + Assert.AreEqual("host", _appSettings.DnsCache["host"].Host); + Assert.IsTrue(_appSettings.DnsCache.Values.Single().IpAddresses.Single().ToString().StartsWith("192.168.")); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/DnsManagerTest.cs b/src/Dns/ProtonVPN.Dns.Tests/DnsManagerTest.cs new file mode 100644 index 000000000..fb4f9679b --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/DnsManagerTest.cs @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using NSubstitute.Core; +using NSubstitute.ExceptionExtensions; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests +{ + [TestClass] + public class DnsManagerTest + { + private const string HOST = "api.protonvpn.ch"; + private const string DIFFERENT_HOST = "differentapi.protonvpn.ch"; + private static readonly TimeSpan FAILED_DNS_REQUEST_TIMEOUT = TimeSpan.FromSeconds(5); + private static readonly TimeSpan NEW_TTL_ON_RESOLVE_ERROR = TimeSpan.FromMinutes(10); + + private MockOfLogger _logger; + private CancellationTokenSource _cancellationTokenSource; + private IDnsOverUdpResolver _dnsOverUdpResolver; + private IDnsOverHttpsResolver _dnsOverHttpsResolver; + private IAppSettings _appSettings; + private IConfiguration _configuration; + private IDnsCacheManager _dnsCacheManager; + private DnsManager _dnsManager; + + [TestInitialize] + public void TestInitialize() + { + _logger = new MockOfLogger(); + _cancellationTokenSource = new CancellationTokenSource(); + _dnsOverUdpResolver = Substitute.For(); + _dnsOverHttpsResolver = Substitute.For(); + _appSettings = Substitute.For(); + _configuration = Substitute.For(); + _configuration.FailedDnsRequestTimeout.Returns(FAILED_DNS_REQUEST_TIMEOUT); + _configuration.NewCacheTimeToLiveOnResolveError.Returns(NEW_TTL_ON_RESOLVE_ERROR); + _dnsCacheManager = new MockOfDnsCacheManager(_appSettings); + _dnsManager = new DnsManager(_dnsOverUdpResolver, _dnsOverHttpsResolver, + _appSettings, _configuration, _logger, _dnsCacheManager); + } + + private DnsResponse CreateDnsResolverResponse(CallInfo arg) + { + string host = arg.ArgAt(0); + return CreateDnsResponse(host); + } + + private DnsResponse CreateDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(12), + new List() + { + new IpAddress(IPAddress.Parse("192.168.1.1")), + new IpAddress(IPAddress.Parse("192.168.1.2")) + }); + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _cancellationTokenSource = null; + _dnsOverUdpResolver = null; + _dnsOverHttpsResolver = null; + _appSettings = null; + _configuration = null; + _dnsManager = null; + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndAllFails() + { + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public async Task TestGetAsync_WhenSpecificHostIsNotCachedAndAllFails() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(DIFFERENT_HOST)); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + private ConcurrentDictionary CreateDnsCache(params DnsResponse[] dnsResponses) + { + ConcurrentDictionary dictionary = new(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + dictionary.TryAdd(dnsResponse.Host, dnsResponse); + } + return dictionary; + } + + [TestMethod] + public async Task TestGetAsync_WhenHasFreshCache() + { + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + Assert.AreEqual(0, _logger.Logs.Count); + await AssertNoResolverWasCalledAsync(); + } + + private void InitializeDnsOverUdpResolver() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(CreateDnsResolverResponse); + } + + private void InitializeDnsOverHttpsResolver() + { + _dnsOverHttpsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(CreateDnsResolverResponse); + } + + private async Task AssertNoResolverWasCalledAsync() + { + await _dnsOverUdpResolver.Received(0).ResolveAsync(Arg.Any(), Arg.Any()); + await AssertHttpsResolverWasNotCalledAsync(); + } + + private async Task AssertHttpsResolverWasNotCalledAsync() + { + await _dnsOverHttpsResolver.Received(0).ResolveAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndUdpResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + private async Task AssertUdpResolverWasCalledAsync() + { + await _dnsOverUdpResolver.Received(1).ResolveAsync(Arg.Any(), Arg.Any()); + await _dnsOverUdpResolver.Received(1).ResolveAsync(HOST, _cancellationTokenSource.Token); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndHttpsResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndUdpResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + private DnsResponse CreateExpiredDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(15), + new List() + { + new IpAddress(IPAddress.Parse("192.168.2.1")), + new IpAddress(IPAddress.Parse("192.168.2.2")) + }, DateTime.UtcNow.AddMinutes(-16)); + } + + private void AssertCacheBeforeExecution() + { + Assert.AreEqual("192.168.2.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.2.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(TimeSpan.FromMinutes(15), _appSettings.DnsCache[HOST].TimeToLive); + } + + private void AssertCacheAfterSuccessfulResolve(IList result, DateTime testStartDateTimeUtc) + { + Assert.AreEqual(2, result.Count); + Assert.AreEqual("192.168.1.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.1.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(TimeSpan.FromMinutes(12), _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndHttpsResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + private async Task AssertCalledBothResolversOnceAsync() + { + await AssertUdpResolverWasCalledAsync(); + await _dnsOverHttpsResolver.Received(1).ResolveAsync(Arg.Any(), Arg.Any()); + await _dnsOverHttpsResolver.Received(1).ResolveAsync(HOST, _cancellationTokenSource.Token); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolvesFail() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + private void AssertCacheAfterFailedResolve(DateTime testStartDateTimeUtc) + { + Assert.AreEqual("192.168.2.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.2.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(NEW_TTL_ON_RESOLVE_ERROR, _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndUdpResolveThrows() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndHttpsResolveThrows() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverHttpsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndUdpResolveThrows() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndHttpsResolveThrows() + { + _dnsOverHttpsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasNothingIsCached() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + + IList result1 = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + + IList result2 = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasExpiredCache() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result1 = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + + IList result2 = await _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenAllFails() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + + IList result = await _dnsManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenHttpsResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverHttpsResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenHttpsResolveThrows() + { + _dnsOverHttpsResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenUdpResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + InitializeDnsOverHttpsResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhendUdpResolveThrows() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertCalledBothResolversOnceAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithFreshCache() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + IList expectedIpAddresses = _appSettings.DnsCache[HOST].IpAddresses; + + IList result = _dnsManager.GetFromCache(HOST); + + Assert.AreEqual(expectedIpAddresses.Count, result.Count); + foreach (IpAddress ipAddress in result) + { + expectedIpAddresses.Contains(ipAddress); + } + await AssertNoResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithExpiredCache() + { + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = _dnsManager.GetFromCache(HOST); + + Assert.AreEqual(2, result.Count); + AssertCacheBeforeExecution(); + await AssertNoResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithNoCache() + { + AssertCacheIsEmpty(); + + IList result = _dnsManager.GetFromCache(HOST); + + Assert.AreEqual(0, result.Count); + AssertCacheIsEmpty(); + await AssertNoResolverWasCalledAsync(); + } + + private void AssertCacheIsEmpty() + { + Assert.IsTrue(_appSettings.DnsCache is null || !_appSettings.DnsCache.ContainsKey(HOST)); + } + + [TestMethod] + public async Task TestGetFromCache_WithNoCachedHost() + { + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(DIFFERENT_HOST)); + AssertCacheIsEmpty(); + + IList result = _dnsManager.GetFromCache(HOST); + + Assert.AreEqual(0, result.Count); + AssertCacheIsEmpty(); + await AssertNoResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WithParallelCallsToForceRaceCondition() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(DelayedDnsResolverResponseAsync); + Assert.AreEqual(0, _logger.Logs.Count); + + Task> task1 = _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + Task> task2 = _dnsManager.GetAsync(HOST, _cancellationTokenSource.Token); + + IList result1 = await task1; + IList result2 = await task2; + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertUdpResolverWasCalledAsync(); + await AssertHttpsResolverWasNotCalledAsync(); + } + + private async Task DelayedDnsResolverResponseAsync(CallInfo arg) + { + await Task.Delay(3000); + string host = arg.ArgAt(0); + return CreateDnsResponse(host); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/DnsOverHttpsProvidersManagerTests.cs b/src/Dns/ProtonVPN.Dns.Tests/DnsOverHttpsProvidersManagerTests.cs new file mode 100644 index 000000000..366a9ffe2 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/DnsOverHttpsProvidersManagerTests.cs @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using NSubstitute.Core; +using NSubstitute.ExceptionExtensions; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests +{ + [TestClass] + public class DnsOverHttpsProvidersManagerTests + { + private const string HOST = "dns.protonvpn.ch"; + private const string DIFFERENT_HOST = "differentdns.protonvpn.ch"; + private static readonly TimeSpan FAILED_DNS_REQUEST_TIMEOUT = TimeSpan.FromSeconds(5); + private static readonly TimeSpan NEW_TTL_ON_RESOLVE_ERROR = TimeSpan.FromMinutes(10); + + private MockOfLogger _logger; + private CancellationTokenSource _cancellationTokenSource; + private IDnsOverUdpResolver _dnsOverUdpResolver; + private IAppSettings _appSettings; + private IConfiguration _configuration; + private IDnsCacheManager _dnsCacheManager; + private DnsOverHttpsProvidersManager _dnsOverHttpsProvidersManager; + + [TestInitialize] + public void TestInitialize() + { + _logger = new MockOfLogger(); + _cancellationTokenSource = new CancellationTokenSource(); + _dnsOverUdpResolver = Substitute.For(); + _appSettings = Substitute.For(); + _configuration = Substitute.For(); + _configuration.FailedDnsRequestTimeout.Returns(FAILED_DNS_REQUEST_TIMEOUT); + _configuration.NewCacheTimeToLiveOnResolveError.Returns(NEW_TTL_ON_RESOLVE_ERROR); + _dnsCacheManager = new MockOfDnsCacheManager(_appSettings); + _dnsOverHttpsProvidersManager = new DnsOverHttpsProvidersManager(_dnsOverUdpResolver, + _appSettings, _configuration, _logger, _dnsCacheManager); + } + + private DnsResponse CreateDnsResolverResponse(CallInfo arg) + { + string host = arg.ArgAt(0); + return CreateDnsResponse(host); + } + + private DnsResponse CreateDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(12), + new List() + { + new IpAddress(IPAddress.Parse("192.168.1.1")), + new IpAddress(IPAddress.Parse("192.168.1.2")) + }); + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _cancellationTokenSource = null; + _dnsOverUdpResolver = null; + _appSettings = null; + _configuration = null; + _dnsOverHttpsProvidersManager = null; + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndAllFails() + { + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public async Task TestGetAsync_WhenSpecificHostIsNotCachedAndAllFails() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(DIFFERENT_HOST)); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + } + + private ConcurrentDictionary CreateDnsCache(params DnsResponse[] dnsResponses) + { + ConcurrentDictionary dictionary = new(); + foreach (DnsResponse dnsResponse in dnsResponses) + { + dictionary.TryAdd(dnsResponse.Host, dnsResponse); + } + return dictionary; + } + + [TestMethod] + public async Task TestGetAsync_WhenHasFreshCache() + { + InitializeDnsOverUdpResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + Assert.AreEqual(0, _logger.Logs.Count); + await AssertResolverWasNotCalledAsync(); + } + + private void InitializeDnsOverUdpResolver() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(CreateDnsResolverResponse); + } + + private async Task AssertResolverWasNotCalledAsync() + { + await _dnsOverUdpResolver.Received(0).ResolveAsync(Arg.Any(), Arg.Any()); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + private async Task AssertResolverWasCalledAsync() + { + await _dnsOverUdpResolver.Received(1).ResolveAsync(Arg.Any(), Arg.Any()); + await _dnsOverUdpResolver.Received(1).ResolveAsync(HOST, _cancellationTokenSource.Token); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + private DnsResponse CreateExpiredDnsResponse(string host) + { + return new DnsResponse(host, TimeSpan.FromMinutes(15), + new List() + { + new IpAddress(IPAddress.Parse("192.168.2.1")), + new IpAddress(IPAddress.Parse("192.168.2.2")) + }, DateTime.UtcNow.AddMinutes(-16)); + } + + private void AssertCacheBeforeExecution() + { + Assert.AreEqual("192.168.2.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.2.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(TimeSpan.FromMinutes(15), _appSettings.DnsCache[HOST].TimeToLive); + } + + private void AssertCacheAfterSuccessfulResolve(IList result, DateTime testStartDateTimeUtc) + { + Assert.AreEqual(2, result.Count); + Assert.AreEqual("192.168.1.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.1.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(TimeSpan.FromMinutes(12), _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolveFails() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + private void AssertCacheAfterFailedResolve(DateTime testStartDateTimeUtc) + { + Assert.AreEqual("192.168.2.1", _appSettings.DnsCache[HOST].IpAddresses[0].ToString()); + Assert.AreEqual("192.168.2.2", _appSettings.DnsCache[HOST].IpAddresses[1].ToString()); + Assert.AreEqual(NEW_TTL_ON_RESOLVE_ERROR, _appSettings.DnsCache[HOST].TimeToLive); + Assert.IsTrue(_appSettings.DnsCache[HOST].ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc >= testStartDateTimeUtc); + Assert.IsTrue(_appSettings.DnsCache[HOST].ResponseDateTimeUtc <= DateTime.UtcNow); + } + + [TestMethod] + public async Task TestGetAsync_WhenHasExpiredCacheAndResolveThrows() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(2, result.Count); + AssertCacheAfterFailedResolve(testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WhenNothingIsCachedAndResolveThrows() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasNothingIsCached() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + + IList result1 = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + + IList result2 = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_UsesCacheOnSecondRequest_WhenFirstRequestHasExpiredCache() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result1 = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + + IList result2 = await _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenAllFails() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + + IList result = await _dnsOverHttpsProvidersManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhenResolveSucceeds() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + InitializeDnsOverUdpResolver(); + Assert.AreEqual(0, _logger.Logs.Count); + + IList result = await _dnsOverHttpsProvidersManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + AssertCacheAfterSuccessfulResolve(result, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestResolveWithoutCacheAsync_WhendResolveThrows() + { + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ThrowsForAnyArgs(new Exception("Injected error for testing.")); + + IList result = await _dnsOverHttpsProvidersManager.ResolveWithoutCacheAsync(HOST, _cancellationTokenSource.Token); + + Assert.AreEqual(0, result.Count); + await AssertResolverWasCalledAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithFreshCache() + { + _appSettings.DnsCache = CreateDnsCache(CreateDnsResponse(HOST)); + IList expectedIpAddresses = _appSettings.DnsCache[HOST].IpAddresses; + + IList result = _dnsOverHttpsProvidersManager.GetFromCache(HOST); + + Assert.AreEqual(expectedIpAddresses.Count, result.Count); + foreach (IpAddress ipAddress in result) + { + expectedIpAddresses.Contains(ipAddress); + } + await AssertResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithExpiredCache() + { + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(HOST)); + AssertCacheBeforeExecution(); + + IList result = _dnsOverHttpsProvidersManager.GetFromCache(HOST); + + Assert.AreEqual(2, result.Count); + AssertCacheBeforeExecution(); + await AssertResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestGetFromCache_WithNoCache() + { + AssertCacheIsEmpty(); + + IList result = _dnsOverHttpsProvidersManager.GetFromCache(HOST); + + Assert.AreEqual(0, result.Count); + AssertCacheIsEmpty(); + await AssertResolverWasNotCalledAsync(); + } + + private void AssertCacheIsEmpty() + { + Assert.IsTrue(_appSettings.DnsCache is null || !_appSettings.DnsCache.ContainsKey(HOST)); + } + + [TestMethod] + public async Task TestGetFromCache_WithNoCachedHost() + { + _appSettings.DnsCache = CreateDnsCache(CreateExpiredDnsResponse(DIFFERENT_HOST)); + AssertCacheIsEmpty(); + + IList result = _dnsOverHttpsProvidersManager.GetFromCache(HOST); + + Assert.AreEqual(0, result.Count); + AssertCacheIsEmpty(); + await AssertResolverWasNotCalledAsync(); + } + + [TestMethod] + public async Task TestGetAsync_WithParallelCallsToForceRaceCondition() + { + DateTime testStartDateTimeUtc = DateTime.UtcNow; + _dnsOverUdpResolver.ResolveAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(DelayedDnsResolverResponseAsync); + Assert.AreEqual(0, _logger.Logs.Count); + + Task> task1 = _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + Task> task2 = _dnsOverHttpsProvidersManager.GetAsync(HOST, _cancellationTokenSource.Token); + + IList result1 = await task1; + IList result2 = await task2; + + AssertCacheAfterSuccessfulResolve(result1, testStartDateTimeUtc); + AssertCacheAfterSuccessfulResolve(result2, testStartDateTimeUtc); + await AssertResolverWasCalledAsync(); + } + + private async Task DelayedDnsResolverResponseAsync(CallInfo arg) + { + await Task.Delay(3000); + string host = arg.ArgAt(0); + return CreateDnsResponse(host); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/Log.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/Log.cs new file mode 100644 index 000000000..1f4968af1 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/Log.cs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public class Log + { + public DateTime DateTimeUtc { get; } + public LogSeverity Severity { get; } + public Type EventType { get; } + public string Message { get; } + public Exception Exception { get; } + public string SourceFilePath { get; } + public string SourceMemberName { get; } + public int SourceLineNumber { get; } + + public Log(LogSeverity severity, Type eventType, string message, Exception exception, + string sourceFilePath, string sourceMemberName, int sourceLineNumber) + { + DateTimeUtc = DateTime.UtcNow; + Severity = severity; + EventType = eventType; + Message = message; + Exception = exception; + SourceFilePath = sourceFilePath; + SourceMemberName = sourceMemberName; + SourceLineNumber = sourceLineNumber; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/LogSeverity.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/LogSeverity.cs new file mode 100644 index 000000000..7b358e0f3 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/LogSeverity.cs @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public enum LogSeverity + { + Debug, + Info, + Warn, + Error, + Fatal + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfDnsCacheManager.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfDnsCacheManager.cs new file mode 100644 index 000000000..5e06c2824 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfDnsCacheManager.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public class MockOfDnsCacheManager : IDnsCacheManager + { + private readonly IAppSettings _appSettings; + + public MockOfDnsCacheManager(IAppSettings appSettings) + { + _appSettings = appSettings; + } + + public async Task AddOrReplaceAsync(string host, DnsResponse dnsResponse) + { + if (_appSettings.DnsCache is null) + { + _appSettings.DnsCache = new ConcurrentDictionary() { [host] = dnsResponse }; + return true; + } + else + { + return _appSettings.DnsCache.AddOrUpdate(host, dnsResponse, (_, _) => dnsResponse) == dnsResponse; + } + } + + public async Task UpdateAsync(string host, Func dnsResponseUpdateFactory) + { + DnsResponse result = null; + if (_appSettings.DnsCache is not null) + { + if (_appSettings.DnsCache.TryGetValue(host, out DnsResponse oldDnsResponse)) + { + DnsResponse newDnsResponse = dnsResponseUpdateFactory(oldDnsResponse); + if (_appSettings.DnsCache.TryUpdate(host, newDnsResponse, oldDnsResponse)) + { + result = newDnsResponse; + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfHttpClientFactory.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfHttpClientFactory.cs new file mode 100644 index 000000000..56b915d71 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfHttpClientFactory.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Net.Http; +using ProtonVPN.Dns.HttpClients; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public class MockOfHttpClientFactory : IHttpClientFactory + { + private readonly MockHttpMessageHandler _messageHandler; + + public MockOfHttpClientFactory(MockHttpMessageHandler messageHandler) + { + _messageHandler = messageHandler; + } + + public HttpClient Create() + { + return new HttpClient(_messageHandler); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfLogger.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfLogger.cs new file mode 100644 index 000000000..b2b501277 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfLogger.cs @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization; + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public class MockOfLogger : ILogger + { + private readonly IList _logs = new List(); + public IReadOnlyList Logs => _logs as IReadOnlyList; + private object _lock = new(); + + public IList GetRecentLogs() + { + IList result; + lock (_lock) + { + result = _logs.Select(l => l.Message).ToList(); + } + return result; + } + + public void Debug(string message, Exception exception = null, string sourceFilePath = "", + string sourceMemberName = "", int sourceLineNumber = 0) where TEvent : ILogEvent, new() + { + lock (_lock) + { + _logs.Add(new Log(LogSeverity.Debug, typeof(TEvent), message, exception, sourceFilePath, sourceMemberName, sourceLineNumber)); + } + } + + public void Info(string message, Exception exception = null, string sourceFilePath = "", + string sourceMemberName = "", int sourceLineNumber = 0) where TEvent : ILogEvent, new() + { + lock (_lock) + { + _logs.Add(new Log(LogSeverity.Info, typeof(TEvent), message, exception, sourceFilePath, sourceMemberName, sourceLineNumber)); + } + } + + public void Warn(string message, Exception exception = null, string sourceFilePath = "", + string sourceMemberName = "", int sourceLineNumber = 0) where TEvent : ILogEvent, new() + { + lock (_lock) + { + _logs.Add(new Log(LogSeverity.Warn, typeof(TEvent), message, exception, sourceFilePath, sourceMemberName, sourceLineNumber)); + } + } + + public void Error(string message, Exception exception = null, string sourceFilePath = "", + string sourceMemberName = "", int sourceLineNumber = 0) where TEvent : ILogEvent, new() + { + lock (_lock) + { + _logs.Add(new Log(LogSeverity.Error, typeof(TEvent), message, exception, sourceFilePath, sourceMemberName, sourceLineNumber)); + } + } + + public void Fatal(string message, Exception exception = null, string sourceFilePath = "", + string sourceMemberName = "", int sourceLineNumber = 0) where TEvent : ILogEvent, new() + { + lock (_lock) + { + _logs.Add(new Log(LogSeverity.Fatal, typeof(TEvent), message, exception, sourceFilePath, sourceMemberName, sourceLineNumber)); + } + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersLoader.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersLoader.cs new file mode 100644 index 000000000..0bf357718 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersLoader.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Linq; +using System.Net; +using ProtonVPN.Dns.Contracts.NameServers; + +namespace ProtonVPN.Dns.Tests.Mocks +{ + public class MockOfNameServersLoader : INameServersLoader + { + public const int DNS_PORT = 53; + + private IList _endpoints = new List(); + + public void Set(params IPAddress[] addresses) + { + _endpoints = addresses.Select(a => new IPEndPoint(a, DNS_PORT)).ToList(); + } + + public IList Get() + { + return _endpoints; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Core/OS/Net/DoH/MainHostname.cs b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersResolver.cs similarity index 52% rename from src/ProtonVPN.Core/OS/Net/DoH/MainHostname.cs rename to src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersResolver.cs index ab06c0065..5d22b28a4 100644 --- a/src/ProtonVPN.Core/OS/Net/DoH/MainHostname.cs +++ b/src/Dns/ProtonVPN.Dns.Tests/Mocks/MockOfNameServersResolver.cs @@ -18,38 +18,29 @@ */ using System; -using System.Text; -using Albireo.Base32; -using ProtonVPN.Common.Extensions; +using System.Collections.Generic; +using DnsClient; +using ProtonVPN.Dns.NameServers; -namespace ProtonVPN.Core.OS.Net.DoH +namespace ProtonVPN.Dns.Tests.Mocks { - public class MainHostname + public class MockOfNameServersResolver : INameServersResolver { - private readonly string _apiUrl; + private readonly Func> _expectedNameServersFunc; - private string _hostFormat = "d{0}.protonpro.xyz"; - - public MainHostname(string apiUrl) + public MockOfNameServersResolver(IReadOnlyCollection expectedNameServers) { - _apiUrl = apiUrl; + _expectedNameServersFunc = () => expectedNameServers; } - public string Value(string uid) + public MockOfNameServersResolver(Exception exception) { - string result = string.Format(_hostFormat, GetBase32Host()); - if (!uid.IsNullOrEmpty()) - { - result = $"{uid}.{result}"; - } - - return result; + _expectedNameServersFunc = () => throw exception; } - - private string GetBase32Host() + + public IReadOnlyCollection Resolve() { - Uri apiUri = new Uri(_apiUrl); - return Base32.Encode(Encoding.UTF8.GetBytes(apiUri.Host)).TrimEnd('='); + return _expectedNameServersFunc(); } } } \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/NameServers/NameServersLoaderTest.cs b/src/Dns/ProtonVPN.Dns.Tests/NameServers/NameServersLoaderTest.cs new file mode 100644 index 000000000..e3caf70f5 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/NameServers/NameServersLoaderTest.cs @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using DnsClient; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Dns.NameServers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests.NameServers +{ + [TestClass] + public class NameServersLoaderTest + { + [TestMethod] + public void TestGet() + { + MockOfLogger logger = new(); + NameServersResolver realNameServersResolver = new(); + NameServersLoader nameServersLoader = new(realNameServersResolver, logger); + + IList result = nameServersLoader.Get(); + + Assert.IsTrue(result.Count > 0); + result.ForEach(AssertIPEndPoint); + } + + private void AssertIPEndPoint(IPEndPoint endpoint) + { + Assert.AreEqual(MockOfNameServersLoader.DNS_PORT, endpoint.Port); + Assert.AreEqual(AddressFamily.InterNetwork, endpoint.AddressFamily); + Assert.AreNotEqual(IPAddress.Loopback, endpoint.Address); + Assert.AreNotEqual(IPAddress.Any, endpoint.Address); + Assert.AreNotEqual(IPAddress.Broadcast, endpoint.Address); + Assert.AreNotEqual(IPAddress.None, endpoint.Address); + } + + [TestMethod] + public void TestGet_WithWrongIpAddresses() + { + MockOfLogger logger = new(); + IReadOnlyCollection expectedNameServers = new List + { + new NameServer(IPAddress.None), + new NameServer(IPAddress.Any), + new NameServer(IPAddress.Broadcast), + new NameServer(IPAddress.Loopback) + }; + MockOfNameServersResolver mockOfNameServersResolver = new(expectedNameServers); + NameServersLoader nameServersLoader = new(mockOfNameServersResolver, logger); + + IList result = nameServersLoader.Get(); + + Assert.IsTrue(result.Count == 0); + } + + [TestMethod] + public void TestGet_WithWrongAndCorrectIpAddresses() + { + MockOfLogger logger = new(); + IReadOnlyCollection expectedNameServers = new List + { + new NameServer(IPAddress.None), + new NameServer(IPAddress.Parse("192.168.123.123")), + new NameServer(IPAddress.Any), + new NameServer(IPAddress.Parse("172.18.231.231")), + new NameServer(IPAddress.Broadcast), + new NameServer(IPAddress.Parse("10.99.99.99")), + new NameServer(IPAddress.Loopback) + }; + MockOfNameServersResolver mockOfNameServersResolver = new(expectedNameServers); + NameServersLoader nameServersLoader = new(mockOfNameServersResolver, logger); + + IList result = nameServersLoader.Get(); + + Assert.AreEqual(3, result.Count); + result.ForEach(AssertIPEndPoint); + } + + [TestMethod] + public void TestGet_WithCorrectIpAddresses() + { + MockOfLogger logger = new(); + IReadOnlyCollection expectedNameServers = new List + { + new NameServer(IPAddress.Parse("192.168.123.123")), + new NameServer(IPAddress.Parse("172.18.231.231")), + new NameServer(IPAddress.Parse("10.99.99.99")) + }; + MockOfNameServersResolver mockOfNameServersResolver = new(expectedNameServers); + NameServersLoader nameServersLoader = new(mockOfNameServersResolver, logger); + + IList result = nameServersLoader.Get(); + + Assert.AreEqual(3, result.Count); + result.ForEach(AssertIPEndPoint); + } + + [TestMethod] + public void TestGet_WithExceptionOnResolver() + { + MockOfLogger logger = new(); + MockOfNameServersResolver mockOfNameServersResolver = new(new Exception("Test")); + NameServersLoader nameServersLoader = new(mockOfNameServersResolver, logger); + + IList result = nameServersLoader.Get(); + + Assert.AreEqual(0, result.Count); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/ProtonVPN.Dns.Tests.csproj b/src/Dns/ProtonVPN.Dns.Tests/ProtonVPN.Dns.Tests.csproj new file mode 100644 index 000000000..505ab00ae --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/ProtonVPN.Dns.Tests.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {F6B47679-3362-4A0C-BB4D-BD3C9B0550AF} + Library + Properties + ProtonVPN.Dns.Tests + ProtonVPN.Dns.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {ca44b51d-7645-413a-818f-2c5b57db67dd} + ProtonVPN.Core + + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + + + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75} + ProtonVPN.Dns + + + + + 1.5.0 + + + 2.1.2 + + + 2.1.2 + + + 4.3.0 + + + 5.0.0 + + + + + \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTest.cs b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTest.cs new file mode 100644 index 000000000..d9dcf989c --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTest.cs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests.Resolvers +{ + [TestClass] + public class DnsOverHttpsResolverTest + : DnsOverHttpsResolverTestBase + { + private const string HOST = "api.protonvpn.ch"; + + public DnsOverHttpsResolverTest() : base(HOST) + { + } + + protected override DnsOverHttpsResolver CreateResolver(IConfiguration configuration, + MockOfLogger logger, MockOfHttpClientFactory mockOfHttpClientFactory, + IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager) + { + return new DnsOverHttpsResolver(configuration, logger, + mockOfHttpClientFactory, dnsOverHttpsProvidersManager); + } + + protected override void AssertCorrectResponse(DnsResponse response) + { + Assert.IsTrue(response.IpAddresses.Count > 0); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTestBase.cs b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTestBase.cs new file mode 100644 index 000000000..348d5baa2 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsResolverTestBase.cs @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using ARSoft.Tools.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Networking; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Dns.Tests.Resolvers +{ + [TestClass] + public abstract class DnsOverHttpsResolverTestBase + where T : DnsOverHttpsResolverBase + { + private const string TEST_TOO_QUICK_MESSAGE = + "The test completed too quickly, meaning the request did not took several seconds before timing out."; + private const string TEST_CANCEL_TOOK_TOO_LONG_MESSAGE = + "The test took too long to complete, meaning the cancellation is not working."; + + private readonly string _host; + private readonly IList _dohProviders = new List() + { + "https://dns11.quad9.net/dns-query", + "https://dns.google/dns-query", + }; + private readonly IList _ipAddresses = new List() + { + new IpAddress(IPAddress.Parse("192.168.1.1")), + new IpAddress(IPAddress.Parse("192.168.2.2")), + new IpAddress(IPAddress.Parse("192.168.3.3")), + }; + + private MockOfLogger _logger; + private CancellationTokenSource _cancellationTokenSource; + private Stopwatch _stopwatch; + private MockHttpMessageHandler _mockHttpMessageHandler; + private MockOfHttpClientFactory _httpClientFactory; + private IDnsOverHttpsProvidersManager _mockOfDnsOverHttpsProvidersManager; + private IConfiguration _configuration; + private T _resolver; + + protected DnsOverHttpsResolverTestBase(string host) + { + _host = host; + } + + [TestInitialize] + public void TestInitialize() + { + _logger = new MockOfLogger(); + _cancellationTokenSource = new CancellationTokenSource(); + _stopwatch = new Stopwatch(); + _mockHttpMessageHandler = new MockHttpMessageHandler(); + _httpClientFactory = new MockOfHttpClientFactory(_mockHttpMessageHandler); + _mockOfDnsOverHttpsProvidersManager = Substitute.For(); + _configuration = Substitute.For(); + _configuration.DohClientTimeout.Returns(TimeSpan.FromSeconds(10)); + _configuration.DnsOverHttpsPerProviderTimeout.Returns(TimeSpan.FromSeconds(20)); + _configuration.DnsResolveTimeout.Returns(TimeSpan.FromSeconds(30)); + _configuration.DefaultDnsTimeToLive.Returns(TimeSpan.FromMinutes(20)); + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _cancellationTokenSource = null; + _stopwatch = null; + _mockHttpMessageHandler = null; + _httpClientFactory = null; + _mockOfDnsOverHttpsProvidersManager = null; + _configuration = null; + _resolver = null; + } + + [TestMethod] + public async Task TestResolveAsync() + { + InitializeResolverCorrectly(); + InitializeSuccessfulRequests(); + + DnsResponse response = await ExecuteAsync(_host); + + AssertCorrectResponse(response); + Assert.IsTrue(response.TimeToLive > TimeSpan.Zero); + Assert.IsTrue(response.ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(response.ResponseDateTimeUtc <= DateTime.UtcNow); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(9)); + } + + private void InitializeSuccessfulRequests() + { + foreach (IpAddress ipAddress in _ipAddresses) + { + _mockHttpMessageHandler + .When($"https://{ipAddress}/dns-query?dns=AAABAAABAAAAAAAAA2FwaQlwcm90b252cG4CY2gAAAEAAQ") + .Respond(_ => new(HttpStatusCode.OK) + { + Content = new ByteArrayContent( + "AACBgAABAAEAAAAAA2FwaQlwcm90b252cG4CY2gAAAEAAcAMAAEAAQAAAfAABLmfn6o=".FromBase64String()) + }); + + _mockHttpMessageHandler + .When($"https://{ipAddress}/dns-query?dns=AAABAAABAAAAAAAAG2RNRllHU0xUUU9KWFhJMzNPT1pZRzRMVEROQQlwcm90b25wcm8DeHl6AAAQAAE") + .Respond(_ => new(HttpStatusCode.OK) + { + Content = new ByteArrayContent( + "AACBgAABAAMAAAAAG2RNRllHU0xUUU9KWFhJMzNPT1pZRzRMVEROQQlwcm90b25wcm8DeHl6AAAQAAHADAAQAAEAAAB4ADMyZWMyLTMtNjUtMjYtMTQ4LmV1LWNlbnRyYWwtMS5jb21wdXRlLmFtYXpvbmF3cy5jb23ADAAQAAEAAAB4ADY1ZWMyLTE4LTE5Mi0xMTUtMTg2LmV1LWNlbnRyYWwtMS5jb21wdXRlLmFtYXpvbmF3cy5jb23ADAAQAAEAAAB4ADQzZWMyLTMtNjgtMjMyLTIwMy5ldS1jZW50cmFsLTEuY29tcHV0ZS5hbWF6b25hd3MuY29t".FromBase64String()) + }); + } + } + + protected abstract void AssertCorrectResponse(DnsResponse response); + + private async Task ExecuteAsync(string host) + { + return await ExecuteWithStopwatchAsync(() => ResolveAsync(host)); + } + + private async Task ResolveAsync(string host) + { + return await _resolver.ResolveAsync(host, _cancellationTokenSource.Token); + } + + private async Task ExecuteWithStopwatchAsync(Func> task) + { + _stopwatch.Start(); + DnsResponse response = await task(); + _stopwatch.Stop(); + return response; + } + + private void InitializeResolverCorrectly() + { + _configuration.DoHProviders.Returns(_dohProviders.ToList()); + _resolver = CreateResolver(_configuration, _logger, _httpClientFactory, _mockOfDnsOverHttpsProvidersManager); + InitializeDnsOverHttpsProvidersManagerReturningSuccessfully(); + } + + private void InitializeDnsOverHttpsProvidersManagerReturningSuccessfully() + { + foreach (string dohProvider in _dohProviders) + { + _mockOfDnsOverHttpsProvidersManager + .GetAsync(dohProvider, Arg.Any()) + .ReturnsForAnyArgs(_ipAddresses.ToList()); + } + } + + protected abstract T CreateResolver(IConfiguration configuration, MockOfLogger logger, + MockOfHttpClientFactory httpClientFactory, IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager); + + [TestMethod] + public async Task TestResolveAsync_WithNonExistentHost() + { + string host = "g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu"; + InitializeResolverCorrectly(); + + DnsResponse response = await ExecuteAsync(host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(15)); + } + + [TestMethod] + public async Task TestResolveAsync_WithEmptyHost() + { + string host = string.Empty; + InitializeResolverCorrectly(); + + DnsResponse response = await ExecuteAsync(host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + [TestMethod] + public async Task TestResolveAsync_WithNullHost() + { + string host = null; + InitializeResolverCorrectly(); + + DnsResponse response = await ExecuteAsync(host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + [TestMethod] + public async Task TestResolveAsync_WithCancelledToken() + { + string host = null; + InitializeResolverCorrectly(); + _cancellationTokenSource.Cancel(); + + DnsResponse response = await ExecuteAsync(host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + [TestMethod] + public async Task TestResolveAsync_WithWrongProviderUrls() + { + InitializeResolverWithWrongProviderUrls(); + + DnsResponse response = await ExecuteAsync(_host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(15)); + } + + private void InitializeResolverWithWrongProviderUrls() + { + _configuration.DoHProviders.Returns(new List() + { + "https://g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu/dns-query", + "https://g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu/dns-query/", + "https://g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu/", + "https://g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu", + }); + _resolver = CreateResolver(_configuration, _logger, _httpClientFactory, _mockOfDnsOverHttpsProvidersManager); + } + + [TestMethod] + public async Task TestResolveAsync_WithWrongProviderIpAddresses() + { + InitializeResolverWithWrongProviderIpAddresses(); + + DnsResponse response = await ExecuteAsync(_host); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(15)); + } + + private void InitializeResolverWithWrongProviderIpAddresses() + { + _configuration.DoHProviders.Returns(new List() + { + "https://192.168.153.153", + "https://192.168.154.154", + "https://192.168.155.155", + }); + _resolver = CreateResolver(_configuration, _logger, _httpClientFactory, _mockOfDnsOverHttpsProvidersManager); + InitializeDnsOverHttpsProvidersManagerTimingOut(); + } + + private void InitializeDnsOverHttpsProvidersManagerTimingOut() + { + foreach (string dohProvider in _dohProviders) + { + _mockOfDnsOverHttpsProvidersManager + .GetAsync(dohProvider, Arg.Any()) + .ReturnsForAnyArgs(async _ => await TimeoutHttpRequestAsync()); + } + } + + private async Task> TimeoutHttpRequestAsync() + { + await Task.Delay(TimeSpan.FromSeconds(10), _cancellationTokenSource.Token); + throw new TimeoutException("Unit test HTTP request timeout"); + } + + [TestMethod] + public async Task TestResolveAsync_WhenCancelled() + { + InitializeResolverWithWrongProviderIpAddresses(); + + _stopwatch.Start(); + Task task = Task.Run(() => _resolver.ResolveAsync(_host, _cancellationTokenSource.Token)); + Task.Delay(TimeSpan.FromSeconds(3)).ContinueWith(_ => _cancellationTokenSource.Cancel()); + DnsResponse response = await task; + _stopwatch.Stop(); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed >= TimeSpan.FromSeconds(3), TEST_TOO_QUICK_MESSAGE); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(5), TEST_CANCEL_TOOK_TOO_LONG_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsTxtRecordsResolverTest.cs b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsTxtRecordsResolverTest.cs new file mode 100644 index 000000000..bc8572a6e --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverHttpsTxtRecordsResolverTest.cs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests.Resolvers +{ + [TestClass] + public class DnsOverHttpsTxtRecordsResolverTest + : DnsOverHttpsResolverTestBase + { + private const string HOST = "dMFYGSLTQOJXXI33OOZYG4LTDNA.protonpro.xyz"; + + public DnsOverHttpsTxtRecordsResolverTest() : base(HOST) + { + } + + protected override DnsOverHttpsTxtRecordsResolver CreateResolver(IConfiguration configuration, + MockOfLogger logger, MockOfHttpClientFactory mockOfHttpClientFactory, + IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager) + { + return new DnsOverHttpsTxtRecordsResolver(configuration, logger, + mockOfHttpClientFactory, dnsOverHttpsProvidersManager); + } + + protected override void AssertCorrectResponse(DnsResponse response) + { + Assert.IsTrue(response.AlternativeHosts.Count > 0); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverUdpResolverTest.cs b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverUdpResolverTest.cs new file mode 100644 index 000000000..dadb20c65 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/Resolvers/DnsOverUdpResolverTest.cs @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Diagnostics; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.NameServers; +using ProtonVPN.Dns.Resolvers; +using ProtonVPN.Dns.Tests.Mocks; + +namespace ProtonVPN.Dns.Tests.Resolvers +{ + [TestClass] + public class DnsOverUdpResolverTest + { + private const string HOST = "api.protonvpn.ch"; + private static readonly TimeSpan DNS_RESOLVE_TIMEOUT = TimeSpan.FromSeconds(30); + private static readonly TimeSpan DEFAULT_DNS_TTL = TimeSpan.FromMinutes(20); + + private MockOfLogger _logger; + private CancellationTokenSource _cancellationTokenSource; + private Stopwatch _stopwatch; + private IConfiguration _configuration; + private MockOfNameServersLoader _mockOfNameServersLoader; + private DnsOverUdpResolver _resolver; + + [TestInitialize] + public void TestInitialize() + { + _logger = new MockOfLogger(); + _cancellationTokenSource = new CancellationTokenSource(); + _stopwatch = new Stopwatch(); + _configuration = Substitute.For(); + _configuration.DnsResolveTimeout.Returns(DNS_RESOLVE_TIMEOUT); + _configuration.DefaultDnsTimeToLive.Returns(DEFAULT_DNS_TTL); + } + + [TestCleanup] + public void TestCleanup() + { + _logger = null; + _cancellationTokenSource = null; + _stopwatch = null; + _configuration = null; + _mockOfNameServersLoader = null; + _resolver = null; + } + + [TestMethod] + public async Task TestResolveAsync() + { + InitializeResolverWithRealNameServersLoader(); + + DnsResponse response = await ExecuteAsync(); + + Assert.IsTrue(response.IpAddresses.Count > 0); + Assert.IsTrue(response.TimeToLive > TimeSpan.Zero); + Assert.IsTrue(response.ExpirationDateTimeUtc > DateTime.UtcNow); + Assert.IsTrue(response.ResponseDateTimeUtc <= DateTime.UtcNow); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(5)); + } + + private void InitializeResolverWithRealNameServersLoader() + { + NameServersResolver nameServersResolver = new(); + NameServersLoader nameServersLoader = new(nameServersResolver, _logger); + _resolver = new(nameServersLoader, _configuration, _logger); + } + + private async Task ExecuteAsync() + { + return await ExecuteWithStopwatchAsync(ResolveAsync); + } + + private async Task ResolveAsync() + { + return await _resolver.ResolveAsync(HOST, _cancellationTokenSource.Token); + } + + private async Task ExecuteWithStopwatchAsync(Func> task) + { + _stopwatch.Start(); + DnsResponse response = await task(); + _stopwatch.Stop(); + return response; + } + + [TestMethod] + public async Task TestResolveAsync_WithoutNameServers() + { + InitializeWithMockOfNameServersLoader(); + + DnsResponse response = await ExecuteAsync(); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + private void InitializeWithMockOfNameServersLoader() + { + _mockOfNameServersLoader = new MockOfNameServersLoader(); + _resolver = new DnsOverUdpResolver(_mockOfNameServersLoader, _configuration, _logger); + } + + [TestMethod] + public async Task TestResolveAsync_WithNonWorkingNameServers() + { + InitializeWithMockOfNameServersLoader(); + SetNonWorkingNameServers(); + + DnsResponse response = await ExecuteAsync(); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(20)); + } + + private void SetNonWorkingNameServers() + { + _mockOfNameServersLoader.Set(IPAddress.Loopback, IPAddress.Parse("192.168.153.153")); + } + + [TestMethod] + public async Task TestResolveAsync_WhenCancelled() + { + InitializeWithMockOfNameServersLoader(); + SetNonWorkingNameServers(); + + DnsResponse response = await ExecuteWithStopwatchAsync(StartResolveAndCancelAsync); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed >= TimeSpan.FromSeconds(3) && _stopwatch.Elapsed < TimeSpan.FromSeconds(5)); + } + + private async Task StartResolveAndCancelAsync() + { + Task task = Task.Run(() => _resolver.ResolveAsync(HOST, _cancellationTokenSource.Token)); + Task.Delay(TimeSpan.FromSeconds(3)).ContinueWith(_ => _cancellationTokenSource.Cancel()); + return await task; + } + + [TestMethod] + public async Task TestResolveAsync_WithCancelledToken() + { + InitializeWithMockOfNameServersLoader(); + SetNonWorkingNameServers(); + + _cancellationTokenSource.Cancel(); + DnsResponse response = await ExecuteAsync(); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + [TestMethod] + public async Task TestResolveAsync_WithNonExistentHost() + { + InitializeResolverWithRealNameServersLoader(); + + DnsResponse response = await ExecuteWithStopwatchAndCustomHostAsync( + "g5f16gfds1gdsf5g16dsfg15fs5gfds651d61s651g6516gf1s6fdgfs.vhbverhu"); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(20)); + } + + private async Task ExecuteWithStopwatchAndCustomHostAsync(string host) + { + return await ExecuteWithStopwatchAsync(() => ResolveWithSpecificHostAsync(host)); + } + + private async Task ResolveWithSpecificHostAsync(string host) + { + return await _resolver.ResolveAsync(host, _cancellationTokenSource.Token); + } + + [TestMethod] + public async Task TestResolveAsync_WithEmptyHost() + { + InitializeResolverWithRealNameServersLoader(); + + DnsResponse response = await ExecuteWithStopwatchAndCustomHostAsync(string.Empty); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + + [TestMethod] + public async Task TestResolveAsync_WithNullHost() + { + InitializeResolverWithRealNameServersLoader(); + + DnsResponse response = await ExecuteWithStopwatchAndCustomHostAsync(null); + + Assert.AreEqual(null, response); + Assert.IsTrue(_stopwatch.Elapsed < TimeSpan.FromSeconds(1)); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns.Tests/app.config b/src/Dns/ProtonVPN.Dns.Tests/app.config new file mode 100644 index 000000000..c92446e50 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns.Tests/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/ARecordDnsManagerBase.cs b/src/Dns/ProtonVPN.Dns/ARecordDnsManagerBase.cs new file mode 100644 index 000000000..ef2087b73 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/ARecordDnsManagerBase.cs @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; + +namespace ProtonVPN.Dns +{ + public abstract class ARecordDnsManagerBase + { + protected TimeSpan FailedDnsRequestTimeout { get; } + protected TimeSpan NewCacheTimeToLiveOnResolveError { get; } + protected ILogger Logger { get; } + protected IDnsCacheManager DnsCacheManager { get; } + + private readonly IAppSettings _appSettings; + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly ConcurrentDictionary _failedRequestsCache = new(); + + protected ARecordDnsManagerBase(IAppSettings appSettings, IConfiguration configuration, + ILogger logger, IDnsCacheManager dnsCacheManager) + { + FailedDnsRequestTimeout = configuration.FailedDnsRequestTimeout; + NewCacheTimeToLiveOnResolveError = configuration.NewCacheTimeToLiveOnResolveError; + Logger = logger; + DnsCacheManager = dnsCacheManager; + _appSettings = appSettings; + } + + public async Task> GetAsync(string host, CancellationToken cancellationToken) + { + IList ipAddresses = GetFreshIpAddressesFromCache(host); + if (ipAddresses.Count == 0) + { + ipAddresses = await ResolveOrGetFromCacheAsync(host, cancellationToken); + } + + return ipAddresses; + } + + private async Task> ResolveOrGetFromCacheAsync(string host, CancellationToken cancellationToken) + { + try + { + await _semaphore.WaitAsync(cancellationToken); + } + catch + { + Logger.Warn($"DNS resolve of host '{host}' was cancelled while waiting."); + return new List(); + } + IList ipAddresses; + try + { + ipAddresses = GetFreshIpAddressesFromCache(host); + if (ipAddresses.Count == 0) + { + if (_failedRequestsCache.TryGetValue(host, out DateTime timeoutEndDateUtc) && timeoutEndDateUtc > DateTime.UtcNow) + { + Logger.Debug($"Skipping DNS resolve of host '{host}' because its under timeout."); + ipAddresses = GetIpAddressesFromCache(host); + } + else + { + Logger.Info($"No fresh IP addresses for host '{host}' were found in the cache. Triggering a refresh."); + ipAddresses = await ResolveHostAsync(host, cancellationToken); + if (ipAddresses.Count == 0) + { + ipAddresses = await GetIpAddressesFromCacheAndSetNewTtlAsync(host); + } + } + } + else + { + Logger.Debug($"Locked re-check for a fresh DNS cache of host '{host}' was successful."); + } + } + finally + { + _semaphore.Release(); + } + + return ipAddresses; + } + + private IList GetFreshIpAddressesFromCache(string host) + { + IList ipAddresses = new List(); + DateTime currentDateTimeUtc = DateTime.UtcNow; + + if (_appSettings.DnsCache.TryGetValueIfDictionaryIsNotNull(host, out DnsResponse dnsResponse) && + dnsResponse.ExpirationDateTimeUtc > currentDateTimeUtc) + { + ipAddresses = dnsResponse.IpAddresses; + } + + return ipAddresses; + } + + private IList GetIpAddressesFromCache(string host) + { + IList ipAddresses = new List(); + + if (_appSettings.DnsCache.TryGetValueIfDictionaryIsNotNull(host, out DnsResponse dnsResponse)) + { + ipAddresses = dnsResponse.IpAddresses; + } + + return ipAddresses ?? new List(); + } + + private async Task> GetIpAddressesFromCacheAndSetNewTtlAsync(string host) + { + IList ipAddresses = GetIpAddressesFromCache(host); + + if (ipAddresses.Any()) + { + DnsResponse newDnsResponse = await DnsCacheManager.UpdateAsync(host, SetDatesAndTimeToLiveFactory); + Logger.Info($"Returning cached IP addresses for host '{host}'. " + + $"New TTL of {newDnsResponse.TimeToLive} resulting in a " + + $"new expiration date of {newDnsResponse.ExpirationDateTimeUtc}."); + } + else + { + DateTime timeoutEndDateUtc = DateTime.UtcNow + FailedDnsRequestTimeout; + _failedRequestsCache.AddOrUpdate(host, timeoutEndDateUtc, (_, _) => timeoutEndDateUtc); + Logger.Warn($"No cached IP addresses exist for host '{host}'. " + + $"Next resolve can only be made after '{timeoutEndDateUtc}'."); + } + + return ipAddresses; + } + + private DnsResponse SetDatesAndTimeToLiveFactory(DnsResponse dnsResponse) + { + dnsResponse.SetDatesAndTimeToLive(NewCacheTimeToLiveOnResolveError); + return dnsResponse; + } + + protected abstract Task> ResolveHostAsync(string host, CancellationToken cancellationToken); + + public async Task> ResolveWithoutCacheAsync(string host, CancellationToken cancellationToken) + { + try + { + await _semaphore.WaitAsync(cancellationToken); + } + catch + { + Logger.Warn($"DNS resolve of host '{host}' was cancelled while waiting."); + return new List(); + } + IList ipAddresses = new List(); + try + { + if (_failedRequestsCache.TryGetValue(host, out DateTime timeoutEndDateUtc) && timeoutEndDateUtc > DateTime.UtcNow) + { + Logger.Debug($"Skipping forced DNS resolve of host '{host}' because its under timeout."); + } + else + { + Logger.Info($"Starting resolve of IP addresses for host '{host}'."); + ipAddresses = await ResolveHostAsync(host, cancellationToken); + } + } + finally + { + _semaphore.Release(); + } + return ipAddresses; + } + + public IList GetFromCache(string host) + { + ConcurrentDictionary dnsCache = _appSettings.DnsCache; + IList ipAddresses = new List(); + + if (dnsCache.TryGetValueIfDictionaryIsNotNull(host, out DnsResponse dnsResponse)) + { + ipAddresses = dnsResponse.IpAddresses; + } + + return ipAddresses; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/AlternativeHostsManager.cs b/src/Dns/ProtonVPN.Dns/AlternativeHostsManager.cs new file mode 100644 index 000000000..436715fa2 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/AlternativeHostsManager.cs @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; + +namespace ProtonVPN.Dns +{ + public class AlternativeHostsManager : IAlternativeHostsManager + { + private readonly IDnsOverHttpsTxtRecordsResolver _dnsOverHttpsTxtRecordsResolver; + private readonly IAppSettings _appSettings; + private readonly ILogger _logger; + private readonly IDnsCacheManager _dnsCacheManager; + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly ConcurrentDictionary _failedRequestsCache = new(); + private readonly TimeSpan _failedDnsRequestTimeout; + private readonly TimeSpan _newCacheTimeToLiveOnResolveError; + + public AlternativeHostsManager(IDnsOverHttpsTxtRecordsResolver dnsOverHttpsTxtRecordsResolver, + IAppSettings appSettings, IConfiguration configuration, ILogger logger, IDnsCacheManager dnsCacheManager) + { + _dnsOverHttpsTxtRecordsResolver = dnsOverHttpsTxtRecordsResolver; + _appSettings = appSettings; + _logger = logger; + _dnsCacheManager = dnsCacheManager; + _failedDnsRequestTimeout = configuration.FailedDnsRequestTimeout; + _newCacheTimeToLiveOnResolveError = configuration.NewCacheTimeToLiveOnResolveError; + } + + public async Task> GetAsync(string host, CancellationToken cancellationToken) + { + IList alternativeHosts = GetFreshAlternativeHostsFromCache(host); + if (alternativeHosts.Count == 0) + { + alternativeHosts = await ResolveOrGetFromCacheAsync(host, cancellationToken); + } + + return alternativeHosts; + } + + private async Task> ResolveOrGetFromCacheAsync(string host, CancellationToken cancellationToken) + { + try + { + await _semaphore.WaitAsync(cancellationToken); + } + catch + { + _logger.Warn($"Alternative hosts resolve of host '{host}' was cancelled while waiting."); + return new List(); + } + IList alternativeHosts; + try + { + alternativeHosts = GetFreshAlternativeHostsFromCache(host); + if (alternativeHosts.Count == 0) + { + if (_failedRequestsCache.TryGetValue(host, out DateTime timeoutEndDateUtc) && timeoutEndDateUtc > DateTime.UtcNow) + { + _logger.Debug($"Skipping alternative hosts resolve of host '{host}' because its under timeout."); + alternativeHosts = GetAlternativeHostsFromCache(host); + } + else + { + _logger.Info($"No fresh alternative hosts for host '{host}' were found in the cache. " + + $"Triggering a refresh."); + alternativeHosts = await ResolveHostAsync(host, cancellationToken); + if (alternativeHosts.Count == 0) + { + alternativeHosts = await GetAlternativeHostsFromCacheAndSetNewTtlAsync(host); + } + } + } + else + { + _logger.Debug($"Locked re-check for a fresh alternative " + + $"hosts cache of host '{host}' was successful."); + } + } + finally + { + _semaphore.Release(); + _logger.Debug($"Released semaphore of alternative hosts DNS resolve of host '{host}'."); + } + + return alternativeHosts; + } + + private IList GetFreshAlternativeHostsFromCache(string host) + { + IList alternativeHosts = new List(); + DateTime currentDateTimeUtc = DateTime.UtcNow; + + if (_appSettings.DnsCache.TryGetValueIfDictionaryIsNotNull(host, out DnsResponse dnsResponse) && + dnsResponse.ExpirationDateTimeUtc > currentDateTimeUtc) + { + alternativeHosts = dnsResponse.AlternativeHosts; + } + + return alternativeHosts; + } + + private IList GetAlternativeHostsFromCache(string host) + { + IList alternativeHosts = new List(); + + if (_appSettings.DnsCache.TryGetValueIfDictionaryIsNotNull(host, out DnsResponse dnsResponse)) + { + alternativeHosts = dnsResponse.AlternativeHosts; + } + + return alternativeHosts ?? new List(); + } + + private async Task> GetAlternativeHostsFromCacheAndSetNewTtlAsync(string host) + { + IList alternativeHosts = GetAlternativeHostsFromCache(host); + + if (alternativeHosts.Any()) + { + DnsResponse newDnsResponse = await _dnsCacheManager.UpdateAsync(host, SetDatesAndTimeToLiveFactory); + _logger.Info($"Returning cached alternative hosts for host '{host}'. " + + $"New TTL of {newDnsResponse.TimeToLive} resulting in a " + + $"new expiration date of {newDnsResponse.ExpirationDateTimeUtc}."); + } + else + { + DateTime timeoutEndDateUtc = DateTime.UtcNow + _failedDnsRequestTimeout; + _failedRequestsCache.AddOrUpdate(host, timeoutEndDateUtc, (_, _) => timeoutEndDateUtc); + _logger.Warn($"No cached alternative hosts exist for host '{host}'. " + + $"Next resolve can only be made after '{timeoutEndDateUtc}'."); + } + + return alternativeHosts; + } + + private DnsResponse SetDatesAndTimeToLiveFactory(DnsResponse dnsResponse) + { + dnsResponse.SetDatesAndTimeToLive(_newCacheTimeToLiveOnResolveError); + return dnsResponse; + } + + private async Task> ResolveHostAsync(string host, CancellationToken cancellationToken) + { + try + { + _logger.Info($"Attempting a HTTPS DNS request for TXT records of host '{host}'."); + DnsResponse dnsResponse = await _dnsOverHttpsTxtRecordsResolver.ResolveAsync(host, cancellationToken); + + if (dnsResponse != null && dnsResponse.AlternativeHosts.Any()) + { + _logger.Info($"The HTTPS DNS request for TXT records of host '{host}' was successful. " + + "Saving to cache."); + IList alternativeHosts = dnsResponse.AlternativeHosts; + await _dnsCacheManager.AddOrReplaceAsync(host, dnsResponse); + return alternativeHosts; + } + + _logger.Error($"The HTTPS DNS request for TXT records of host '{host}' was unsuccessful."); + } + catch (Exception e) + { + _logger.Error($"An unexpected error as occurred when resolving " + + $"HTTPS DNS for TXT records of host '{host}'.", e); + } + + return new List(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/AlternativeRouting/AlternativeRoutingHostGenerator.cs b/src/Dns/ProtonVPN.Dns/AlternativeRouting/AlternativeRoutingHostGenerator.cs new file mode 100644 index 000000000..497461523 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/AlternativeRouting/AlternativeRoutingHostGenerator.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Text; +using Albireo.Base32; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Dns.Contracts.AlternativeRouting; + +namespace ProtonVPN.Dns.AlternativeRouting +{ + public class AlternativeRoutingHostGenerator : IAlternativeRoutingHostGenerator + { + private const string HOST_FORMAT = "d{0}.protonpro.xyz"; + + private Lazy _baseHost; + + public AlternativeRoutingHostGenerator(IConfiguration configuration) + { + _baseHost = new Lazy(() => GenerateBaseHost(configuration.Urls.ApiUrl)); + } + + private string GenerateBaseHost(string apiUrl) + { + string base32ApiUrl = CalculateBase32ApiUrl(apiUrl); + return string.Format(HOST_FORMAT, base32ApiUrl); + } + + private string CalculateBase32ApiUrl(string apiUrl) + { + Uri apiUri = new(apiUrl); + return Base32.Encode(Encoding.UTF8.GetBytes(apiUri.Host)).TrimEnd('='); + } + + public string Generate(string uid) + { + return uid.IsNullOrEmpty() ? _baseHost.Value : $"{uid}.{_baseHost.Value}"; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/Caching/DnsCacheManager.cs b/src/Dns/ProtonVPN.Dns/Caching/DnsCacheManager.cs new file mode 100644 index 000000000..29510ad60 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Caching/DnsCacheManager.cs @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Contracts; + +namespace ProtonVPN.Dns.Caching +{ + public class DnsCacheManager : IDnsCacheManager + { + private readonly IAppSettings _appSettings; + private readonly ILogger _logger; + private readonly SemaphoreSlim _semaphore = new(1, 1); + + public DnsCacheManager(IAppSettings appSettings, ILogger logger) + { + _appSettings = appSettings; + _logger = logger; + } + + /// Adds the DnsResponse if the host doesn't exist or replaces the DnsResponse if the host already + /// exists. If the cache null, it initializes it and adds the DnsResponse. Returns true if DnsResponse was + /// added or replaced, and returns false if an exception was handled and ignored. + public async Task AddOrReplaceAsync(string host, DnsResponse dnsResponse) + { + await _semaphore.WaitAsync(); + bool result; + try + { + result = AddOrReplace(host, dnsResponse) == dnsResponse; + } + catch (Exception e) + { + result = false; + _logger.Error($"DNS cache failed to add or replace host '{host}'.", e); + } + finally + { + _semaphore.Release(); + } + + return result; + } + + private DnsResponse AddOrReplace(string host, DnsResponse dnsResponse) + { + ConcurrentDictionary dnsCache = _appSettings.DnsCache; + DnsResponse cachedValue; + if (dnsCache is null) + { + dnsCache = new ConcurrentDictionary() { [host] = dnsResponse }; + cachedValue = dnsResponse; + } + else + { + cachedValue = dnsCache.AddOrUpdate(host, dnsResponse, (_, _) => dnsResponse); + } + _appSettings.DnsCache = dnsCache; + return cachedValue; + } + + /// Updates the value and returns the new value if successful. If it fails, returns null. + public async Task UpdateAsync(string host, Func dnsResponseUpdateFactory) + { + await _semaphore.WaitAsync(); + DnsResponse dnsResponse = null; + try + { + dnsResponse = Update(host, dnsResponseUpdateFactory); + } + catch (Exception e) + { + _logger.Error($"DNS cache failed to update host '{host}'.", e); + } + finally + { + _semaphore.Release(); + } + + return dnsResponse; + } + + private DnsResponse Update(string host, Func dnsResponseUpdateFactory) + { + ConcurrentDictionary dnsCache = _appSettings.DnsCache; + DnsResponse dnsResponse = null; + if (dnsCache is null) + { + _logger.Warn($"DNS cache failed to update host '{host}' because the cache is null."); + } + else + { + if (dnsCache.TryGetValue(host, out DnsResponse oldDnsResponse)) + { + DnsResponse newDnsResponse = dnsResponseUpdateFactory(oldDnsResponse); + if (dnsCache.TryUpdate(host, newDnsResponse, oldDnsResponse)) + { + _appSettings.DnsCache = dnsCache; + dnsResponse = newDnsResponse; + } + else + { + _logger.Error($"DNS cache update operation failed for host '{host}'."); + } + } + else + { + _logger.Warn($"DNS cache failed to update host '{host}' because this host doesn't exist."); + } + } + + return dnsResponse; + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/Caching/IDnsCacheManager.cs b/src/Dns/ProtonVPN.Dns/Caching/IDnsCacheManager.cs new file mode 100644 index 000000000..c7de7424f --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Caching/IDnsCacheManager.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Threading.Tasks; +using ProtonVPN.Dns.Contracts; + +namespace ProtonVPN.Dns.Caching +{ + public interface IDnsCacheManager + { + Task AddOrReplaceAsync(string host, DnsResponse dnsResponse); + Task UpdateAsync(string host, Func dnsResponseUpdateFactory); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/DnsManager.cs b/src/Dns/ProtonVPN.Dns/DnsManager.cs new file mode 100644 index 000000000..fb82ef5dd --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/DnsManager.cs @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; + +namespace ProtonVPN.Dns +{ + public class DnsManager : ARecordDnsManagerBase, IDnsManager + { + private readonly IDnsOverUdpResolver _dnsOverUdpResolver; + private readonly IDnsOverHttpsResolver _dnsOverHttpsResolver; + + public DnsManager(IDnsOverUdpResolver dnsOverUdpResolver, IDnsOverHttpsResolver dnsOverHttpsResolver, + IAppSettings appSettings, IConfiguration configuration, ILogger logger, IDnsCacheManager dnsCacheManager) + : base(appSettings, configuration, logger, dnsCacheManager) + { + _dnsOverUdpResolver = dnsOverUdpResolver; + _dnsOverHttpsResolver = dnsOverHttpsResolver; + } + + protected override async Task> ResolveHostAsync(string host, CancellationToken cancellationToken) + { + IList result = await ResolveDnsAsync("UDP", _dnsOverUdpResolver, host, cancellationToken); + if (result.IsNullOrEmpty()) + { + result = await ResolveDnsAsync("HTTPS", _dnsOverHttpsResolver, host, cancellationToken); + } + + return result ?? new List(); + } + + private async Task> ResolveDnsAsync(string protocol, IDnsResolver dnsResolver, string host, + CancellationToken cancellationToken) + { + try + { + Logger.Info($"Attempting a {protocol} DNS request for host '{host}'."); + DnsResponse dnsResponse = await dnsResolver.ResolveAsync(host, cancellationToken); + + if (dnsResponse != null && dnsResponse.IpAddresses.Any()) + { + Logger.Info($"The {protocol} DNS request was successful for host '{host}'. Saving to cache."); + IList ipAddresses = dnsResponse.IpAddresses; + await DnsCacheManager.AddOrReplaceAsync(host, dnsResponse); + return ipAddresses; + } + + Logger.Error($"The {protocol} DNS request was unsuccessful for host '{host}'."); + } + catch (Exception e) + { + Logger.Error($"An unexpected error as occurred when resolving {protocol} DNS for host '{host}'.", e); + } + + return new List(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/DnsOverHttpsProvidersManager.cs b/src/Dns/ProtonVPN.Dns/DnsOverHttpsProvidersManager.cs new file mode 100644 index 000000000..d50e97e8f --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/DnsOverHttpsProvidersManager.cs @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Core.Settings; +using ProtonVPN.Dns.Caching; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; + +namespace ProtonVPN.Dns +{ + public class DnsOverHttpsProvidersManager : ARecordDnsManagerBase, IDnsOverHttpsProvidersManager + { + private readonly IDnsOverUdpResolver _dnsOverUdpResolver; + + public DnsOverHttpsProvidersManager(IDnsOverUdpResolver dnsOverUdpResolver, + IAppSettings appSettings, IConfiguration configuration, ILogger logger, IDnsCacheManager dnsCacheManager) + : base(appSettings, configuration, logger, dnsCacheManager) + { + _dnsOverUdpResolver = dnsOverUdpResolver; + } + + protected override async Task> ResolveHostAsync(string host, CancellationToken cancellationToken) + { + try + { + Logger.Info($"Attempting a UDP DNS request for host '{host}'."); + DnsResponse dnsResponse = await _dnsOverUdpResolver.ResolveAsync(host, cancellationToken); + + if (dnsResponse != null && dnsResponse.IpAddresses.Any()) + { + Logger.Info($"The UDP DNS request was successful for host '{host}'. Saving to cache."); + IList ipAddresses = dnsResponse.IpAddresses; + await DnsCacheManager.AddOrReplaceAsync(host, dnsResponse); + return ipAddresses; + } + + Logger.Error($"The UDP DNS request was unsuccessful for host '{host}'."); + } + catch (Exception e) + { + Logger.Error($"An unexpected error as occurred when resolving UDP DNS for host '{host}'.", e); + } + + return new List(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/HttpClients/HttpClientFactory.cs b/src/Dns/ProtonVPN.Dns/HttpClients/HttpClientFactory.cs new file mode 100644 index 000000000..777dce079 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/HttpClients/HttpClientFactory.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Net.Http; + +namespace ProtonVPN.Dns.HttpClients +{ + public class HttpClientFactory : IHttpClientFactory + { + public HttpClient Create() + { + return new HttpClient(); + } + } +} \ No newline at end of file diff --git a/src/Api/ProtonVPN.Api/IHttpClientFactory.cs b/src/Dns/ProtonVPN.Dns/HttpClients/IHttpClientFactory.cs similarity index 86% rename from src/Api/ProtonVPN.Api/IHttpClientFactory.cs rename to src/Dns/ProtonVPN.Dns/HttpClients/IHttpClientFactory.cs index ce0adfe9c..a6f7498ee 100644 --- a/src/Api/ProtonVPN.Api/IHttpClientFactory.cs +++ b/src/Dns/ProtonVPN.Dns/HttpClients/IHttpClientFactory.cs @@ -19,11 +19,10 @@ using System.Net.Http; -namespace ProtonVPN.Api +namespace ProtonVPN.Dns.HttpClients { public interface IHttpClientFactory { - HttpClient GetApiHttpClientWithCache(); - HttpClient GetApiHttpClientWithoutCache(); + HttpClient Create(); } } \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/NameServers/INameServersResolver.cs b/src/Dns/ProtonVPN.Dns/NameServers/INameServersResolver.cs new file mode 100644 index 000000000..323404197 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/NameServers/INameServersResolver.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using DnsClient; + +namespace ProtonVPN.Dns.NameServers +{ + public interface INameServersResolver + { + IReadOnlyCollection Resolve(); + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/NameServers/NameServersLoader.cs b/src/Dns/ProtonVPN.Dns/NameServers/NameServersLoader.cs new file mode 100644 index 000000000..113442f9f --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/NameServers/NameServersLoader.cs @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using DnsClient; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Dns.Contracts.NameServers; + +namespace ProtonVPN.Dns.NameServers +{ + public class NameServersLoader : INameServersLoader + { + private readonly INameServersResolver _nameServersResolver; + private readonly ILogger _logger; + + public NameServersLoader(INameServersResolver nameServersResolver, ILogger logger) + { + _nameServersResolver = nameServersResolver; + _logger = logger; + } + + public IList Get() + { + _logger.Info("Getting name servers from available network interfaces."); + IList nameServers; + try + { + nameServers = _nameServersResolver.Resolve() + .Select(NameServerToIPEndPoint) + .Where(e => e is not null && + e.AddressFamily == AddressFamily.InterNetwork && + !Equals(e.Address, IPAddress.Loopback) && + !Equals(e.Address, IPAddress.Any) && + !Equals(e.Address, IPAddress.Broadcast) && + !Equals(e.Address, IPAddress.None)) + .ToList(); + + if (nameServers.Any()) + { + _logger.Info($"Found {nameServers.Count} name servers."); + } + else + { + _logger.Error("Failed to find any name servers."); + } + } + catch (Exception e) + { + nameServers = new List(); + _logger.Error("An error occurred when attempting to find name servers.", e); + } + + return nameServers; + } + + private IPEndPoint NameServerToIPEndPoint(NameServer nameServer) + { + if (nameServer?.Address == null) + { + return null; + } + + IPAddress ipAddress = null; + if (IPAddress.TryParse(nameServer.Address, out IPAddress parsedAddress)) + { + ipAddress = parsedAddress; + _logger.Debug($"Found name server {ipAddress}:{nameServer.Port}."); + } + + return ipAddress == null ? null : new IPEndPoint(ipAddress, nameServer.Port); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/NameServers/NameServersResolver.cs b/src/Dns/ProtonVPN.Dns/NameServers/NameServersResolver.cs new file mode 100644 index 000000000..7604a2849 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/NameServers/NameServersResolver.cs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using DnsClient; + +namespace ProtonVPN.Dns.NameServers +{ + public class NameServersResolver : INameServersResolver + { + public IReadOnlyCollection Resolve() + { + return NameServer.ResolveNameServers( + skipIPv6SiteLocal: true, + fallbackToGooglePublicDns: false); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/ProtonVPN.Dns.csproj b/src/Dns/ProtonVPN.Dns/ProtonVPN.Dns.csproj new file mode 100644 index 000000000..8e0305d69 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/ProtonVPN.Dns.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {8C4C72C3-DCC4-43B1-A2B3-DF77B435FA75} + Library + Properties + ProtonVPN.Dns + ProtonVPN.Dns + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {CA44B51D-7645-413A-818F-2C5B57DB67DD} + ProtonVPN.Core + + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + + + + + + + + 1.2.0 + + + 2.2.9 + + + 1.5.0 + + + + + \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsParallelHttpRequestConfiguration.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsParallelHttpRequestConfiguration.cs new file mode 100644 index 000000000..394f09d58 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsParallelHttpRequestConfiguration.cs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +namespace ProtonVPN.Dns.Resolvers +{ + public class DnsOverHttpsParallelHttpRequestConfiguration + { + public string Host { get; set; } + public string Message { get; set; } + public string ProviderUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolver.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolver.cs new file mode 100644 index 000000000..3ea48c865 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolver.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Net; +using ARSoft.Tools.Net.Dns; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; +using ProtonVPN.Dns.HttpClients; + +namespace ProtonVPN.Dns.Resolvers +{ + public class DnsOverHttpsResolver : DnsOverHttpsResolverBase, IDnsOverHttpsResolver + { + public DnsOverHttpsResolver(IConfiguration configuration, ILogger logger, + IHttpClientFactory httpClientFactory, IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager) + : base(configuration, logger, httpClientFactory, dnsOverHttpsProvidersManager, RecordType.A) + { + } + + protected override bool IsNullOrEmpty(DnsResponse dnsResponse) + { + return dnsResponse == null || dnsResponse.IpAddresses.IsNullOrEmpty(); + } + + protected override DnsResponse ParseDnsResponseMessage(DnsOverHttpsParallelHttpRequestConfiguration config, + DnsMessage dnsResponseMessage) + { + IList ipAddresses = new List(); + int? timeToLiveInSeconds = null; + foreach (DnsRecordBase record in dnsResponseMessage.AnswerRecords) + { + if (record is ARecord aRecord) + { + ipAddresses.Add(aRecord.Address); + if (aRecord.TimeToLive > 0 && (timeToLiveInSeconds == null || timeToLiveInSeconds.Value > aRecord.TimeToLive)) + { + timeToLiveInSeconds = aRecord.TimeToLive; + } + } + } + + Logger.Info($"{ipAddresses.Count} records were received for host '{config.Host}' " + + $"with DNS over HTTPS provider '{config.ProviderUrl}'. TTL is {timeToLiveInSeconds} seconds."); + + return CreateDnsResponseWithIpAddresses(config.Host, timeToLiveInSeconds, ipAddresses); + } + } +} diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolverBase.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolverBase.cs new file mode 100644 index 000000000..eb843518d --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsResolverBase.cs @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using ARSoft.Tools.Net; +using ARSoft.Tools.Net.Dns; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Exceptions; +using ProtonVPN.Dns.HttpClients; + +namespace ProtonVPN.Dns.Resolvers +{ + public abstract class DnsOverHttpsResolverBase : DnsResolverBase + { + private readonly TimeSpan _dnsOverHttpsPerProviderTimeout; + private readonly IDnsOverHttpsProvidersManager _dnsOverHttpsProvidersManager; + private readonly RecordType _recordType; + private readonly IList _providersUrl; + private readonly HttpClient _httpClient; + + protected DnsOverHttpsResolverBase(IConfiguration configuration, ILogger logger, + IHttpClientFactory httpClientFactory, IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager, + RecordType recordType) : base(configuration, logger) + { + _dnsOverHttpsPerProviderTimeout = configuration.DnsOverHttpsPerProviderTimeout; + _dnsOverHttpsProvidersManager = dnsOverHttpsProvidersManager; + _recordType = recordType; + _providersUrl = configuration.DoHProviders?.ToList() ?? new List(); + _httpClient = httpClientFactory.Create(); + _httpClient.Timeout = configuration.DohClientTimeout; + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/dns-message")); + } + + protected override async Task StartTasksAndWaitAnySuccessAsync(string host, + CancellationToken cancellationToken) + { + DnsResponse dnsResponse = null; + try + { + string message = GenerateBase64DnsMessage(host); + List>> resolveFuncs = new(); + foreach (string providerUrl in _providersUrl) + { + DnsOverHttpsParallelHttpRequestConfiguration requestConfig = new() + { + Host = host, + Message = message, + ProviderUrl = providerUrl, + }; + resolveFuncs.Add(ct => TryRequestAsync(requestConfig, ct)); + } + dnsResponse = await WaitAnySuccessfulResolveAsync(resolveFuncs, DnsResolveTimeout, cancellationToken); + } + catch (Exception e) + { + Logger.Error("DNS over HTTPS failed during preparation.", e); + } + + return dnsResponse; + } + + private string GenerateBase64DnsMessage(string host) + { + byte[] bytes = GenerateDnsQueryMessageBytes(host); + byte[] cleanBytes = bytes.TrimTrailingZeroBytes(); + + return Convert.ToBase64String(cleanBytes) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); + } + + private byte[] GenerateDnsQueryMessageBytes(string host) + { + DnsMessage dnsMessage = new(); + DnsQuestion question = new(DomainName.Parse(host), _recordType, RecordClass.INet); + dnsMessage.Questions.Add(question); + dnsMessage.IsRecursionDesired = true; + + return EncodeDnsQueryMessageToBytes(dnsMessage); + } + + private byte[] EncodeDnsQueryMessageToBytes(DnsMessage message) + { + MethodInfo m = message.GetType().GetMethod( + "Encode", + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new[] { typeof(bool), typeof(byte[]).MakeByRefType() }, + null); + + object[] args = { false, null }; + m.Invoke(message, args); + + return args[1] as byte[]; + } + + private async Task TryRequestAsync(DnsOverHttpsParallelHttpRequestConfiguration config, + CancellationToken cancellationToken) + { + Logger.Info($"Attempting to resolve host '{config.Host}' " + + $"with DNS over HTTPS provider '{config.ProviderUrl}'."); + DnsResponse dnsResponse = null; + try + { + dnsResponse = await RequestAsync(config, cancellationToken); + } + catch (Exception e) + { + if (e.IsOrAnyInnerIsOfExceptionType()) + { + LogOperationCancelled($"The DNS over HTTPS provider '{config.ProviderUrl}' " + + $"was canceled when resolving host '{config.Host}'."); + } + else + { + Logger.Error($"The DNS over HTTPS provider '{config.ProviderUrl}' " + + $"failed when resolving host '{config.Host}'.", e); + } + } + + return dnsResponse; + } + + private async Task RequestAsync(DnsOverHttpsParallelHttpRequestConfiguration config, + CancellationToken cancellationToken) + { + IList ipAddresses = await GetDoHProviderIpAddressesAsync(config, cancellationToken); + List>> resolveFuncs = new(); + foreach (IpAddress ipAddress in ipAddresses) + { + resolveFuncs.Add(ct => ResolveForHostAsync(ipAddress, config, cancellationToken)); + } + return await WaitAnySuccessfulResolveAsync(resolveFuncs, _dnsOverHttpsPerProviderTimeout, cancellationToken); + } + + private async Task> GetDoHProviderIpAddressesAsync( + DnsOverHttpsParallelHttpRequestConfiguration config, CancellationToken cancellationToken) + { + UriBuilder providerUriBuilder = new(config.ProviderUrl); + IList ipAddresses = await _dnsOverHttpsProvidersManager.GetAsync(providerUriBuilder.Host, cancellationToken); + if (ipAddresses.IsNullOrEmpty()) + { + throw new DnsException($"No IP addresses were found for host '{config.ProviderUrl}'."); + } + + return ipAddresses; + } + + private async Task ResolveForHostAsync(IpAddress ipAddress, + DnsOverHttpsParallelHttpRequestConfiguration config, CancellationToken cancellationToken) + { + HttpRequestMessage request = CreateRequestMessage(ipAddress, config); + HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken); + ThrowIfHttpResponseMessageIsNotSuccess(response, config); + byte[] dnsResponseMessageBytes = await response.Content.ReadAsByteArrayAsync(); + Logger.Info($"Successfully resolved host '{config.Host}' " + + $"with DNS over HTTPS provider '{config.ProviderUrl}'."); + DnsMessage dnsMessage = DnsMessage.Parse(dnsResponseMessageBytes); + return ParseDnsResponseMessage(config, dnsMessage); + } + + private void ThrowIfHttpResponseMessageIsNotSuccess(HttpResponseMessage response, + DnsOverHttpsParallelHttpRequestConfiguration config) + { + if (!response.IsSuccessStatusCode) + { + throw new HttpException((int)response.StatusCode, $"Unexpected HTTP response '{response.StatusCode}' " + + $"when resolving for host '{config.Host}' with DNS over HTTPS provider '{config.ProviderUrl}'."); + } + } + + private HttpRequestMessage CreateRequestMessage(IpAddress ipAddress, + DnsOverHttpsParallelHttpRequestConfiguration config) + { + UriBuilder dohProviderUriBuilder = new UriBuilder(config.ProviderUrl); + HttpRequestMessage requestMessage = new(); + UriBuilder uriBuilder = new(config.ProviderUrl) + { + Host = ipAddress.ToString(), + Path = $"{dohProviderUriBuilder.Path}", + Query = $"dns={config.Message}", + }; + requestMessage.Headers.Host = dohProviderUriBuilder.Host; + requestMessage.RequestUri = uriBuilder.Uri; + requestMessage.Method = HttpMethod.Get; + + return requestMessage; + } + + protected abstract DnsResponse ParseDnsResponseMessage(DnsOverHttpsParallelHttpRequestConfiguration config, + DnsMessage dnsResponseMessage); + } +} diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsTxtRecordsResolver.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsTxtRecordsResolver.cs new file mode 100644 index 000000000..e4df8834d --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverHttpsTxtRecordsResolver.cs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Collections.Generic; +using ARSoft.Tools.Net.Dns; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.Resolvers; +using ProtonVPN.Dns.HttpClients; + +namespace ProtonVPN.Dns.Resolvers +{ + public class DnsOverHttpsTxtRecordsResolver : DnsOverHttpsResolverBase, IDnsOverHttpsTxtRecordsResolver + { + public DnsOverHttpsTxtRecordsResolver(IConfiguration configuration, ILogger logger, + IHttpClientFactory httpClientFactory, IDnsOverHttpsProvidersManager dnsOverHttpsProvidersManager) + : base(configuration, logger, httpClientFactory, dnsOverHttpsProvidersManager, RecordType.Txt) + { + } + + protected override bool IsNullOrEmpty(DnsResponse dnsResponse) + { + return dnsResponse == null || dnsResponse.AlternativeHosts.IsNullOrEmpty(); + } + + protected override DnsResponse ParseDnsResponseMessage(DnsOverHttpsParallelHttpRequestConfiguration config, + DnsMessage dnsResponseMessage) + { + IList alternativeHosts = new List(); + int? timeToLiveInSeconds = null; + foreach (DnsRecordBase record in dnsResponseMessage.AnswerRecords) + { + if (record is TxtRecord txtRecord) + { + alternativeHosts.Add(txtRecord.TextData); + if (txtRecord.TimeToLive > 0 && + (timeToLiveInSeconds == null || timeToLiveInSeconds.Value > txtRecord.TimeToLive)) + { + timeToLiveInSeconds = txtRecord.TimeToLive; + } + } + } + + Logger.Info($"{alternativeHosts.Count} TXT records were received for host '{config.Host}' " + + $"with DNS over HTTPS provider '{config.ProviderUrl}'. TTL is {timeToLiveInSeconds} seconds."); + + return CreateDnsResponseWithAlternativeHosts(config.Host, timeToLiveInSeconds, alternativeHosts); + } + } +} diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverUdpResolver.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverUdpResolver.cs new file mode 100644 index 000000000..a5dbb8160 --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsOverUdpResolver.cs @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using DnsClient; +using DnsClient.Protocol; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Dns.Contracts; +using ProtonVPN.Dns.Contracts.NameServers; +using ProtonVPN.Dns.Contracts.Resolvers; + +namespace ProtonVPN.Dns.Resolvers +{ + public class DnsOverUdpResolver : DnsResolverBase, IDnsOverUdpResolver + { + private readonly INameServersLoader _nameServersLoader; + + public DnsOverUdpResolver(INameServersLoader nameServersLoader, IConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + _nameServersLoader = nameServersLoader; + } + + protected override async Task StartTasksAndWaitAnySuccessAsync(string host, + CancellationToken cancellationToken) + { + IEnumerable nameServersIpAddresses = _nameServersLoader.Get(); + + List>> resolveFuncs = new(); + foreach (IPEndPoint nameServerIpAddress in nameServersIpAddresses) + { + resolveFuncs.Add(ct => TryResolveAsync(nameServerIpAddress, host, ct)); + } + + return await WaitAnySuccessfulResolveAsync(resolveFuncs, DnsResolveTimeout, cancellationToken); + } + + private async Task TryResolveAsync(IPEndPoint nameServerIpAddress, string host, + CancellationToken cancellationToken) + { + Logger.Info($"Attempting to resolve host '{host}' through '{nameServerIpAddress}'."); + DnsResponse dnsResponse = null; + try + { + dnsResponse = await ResolveAsync(nameServerIpAddress, host, cancellationToken); + } + catch (Exception e) + { + if (e.IsOrAnyInnerIsOfExceptionType()) + { + LogOperationCancelled($"The DNS over UDP resolver through '{nameServerIpAddress}' " + + $"was canceled when resolving host '{host}'."); + } + else if (e is DnsResponseException) + { + Logger.Warn($"DNS failed to get a response from '{nameServerIpAddress}'.", e); + } + else + { + Logger.Error("Unexpected error in DNS task wait.", e); + } + } + + return dnsResponse; + } + + private async Task ResolveAsync(IPEndPoint nameServerIpAddress, string host, + CancellationToken cancellationToken) + { + LookupClientOptions lookupClientOptions = CreateLookupClientOptions(nameServerIpAddress); + ILookupClient lookupClient = new LookupClient(lookupClientOptions); + IDnsQueryResponse dnsQueryResponse = + await lookupClient.QueryAsync(host, QueryType.A, cancellationToken: cancellationToken); + if (dnsQueryResponse.HasError) + { + throw new DnsResponseException((DnsResponseCode)dnsQueryResponse.Header.ResponseCode, dnsQueryResponse.ErrorMessage); + } + + Logger.Info($"The endpoint '{nameServerIpAddress}' responded successfully to the DNS query of host '{host}'."); + IList aRecords = dnsQueryResponse.Answers.ARecords().ToList(); + IList ipAddresses = aRecords.Select(ar => ar.Address.MapToIPv4()) + .Where(ia => !Equals(ia, IPAddress.None) && !Equals(ia, IPAddress.Loopback)) + .ToList(); + int timeToLiveInSeconds = aRecords.Select(ar => ar.InitialTimeToLive).Where(ttl => ttl > 0).DefaultIfEmpty().Min(); + + Logger.Info($"{ipAddresses.Count} records were received for host '{host}' " + + $"with DNS over UDP endpoint '{nameServerIpAddress}'. TTL is {timeToLiveInSeconds} seconds."); + + return CreateDnsResponseWithIpAddresses(host, timeToLiveInSeconds, ipAddresses); + } + + private LookupClientOptions CreateLookupClientOptions(IPEndPoint nameServerIpAddress) + { + return new LookupClientOptions(nameServerIpAddress) + { + UseTcpOnly = false, + UseTcpFallback = true, + Timeout = TimeSpan.FromSeconds(5), + UseCache = false, + CacheFailedResults = true, + FailedResultsCacheDuration = TimeSpan.FromSeconds(10), + Retries = 2, + Recursion = true, + UseRandomNameServer = false, + ThrowDnsErrors = true, + }; + } + + protected override bool IsNullOrEmpty(DnsResponse dnsResponse) + { + return dnsResponse == null || dnsResponse.IpAddresses.IsNullOrEmpty(); + } + } +} \ No newline at end of file diff --git a/src/Dns/ProtonVPN.Dns/Resolvers/DnsResolverBase.cs b/src/Dns/ProtonVPN.Dns/Resolvers/DnsResolverBase.cs new file mode 100644 index 000000000..301ea837a --- /dev/null +++ b/src/Dns/ProtonVPN.Dns/Resolvers/DnsResolverBase.cs @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using ProtonVPN.Common.Configuration; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.DnsLogs; +using ProtonVPN.Common.Networking; +using ProtonVPN.Dns.Contracts; + +namespace ProtonVPN.Dns.Resolvers +{ + public abstract class DnsResolverBase + { + protected ILogger Logger { get; } + protected TimeSpan DnsResolveTimeout { get; } + + private readonly TimeSpan _defaultDnsTimeToLive; + + protected DnsResolverBase(IConfiguration configuration, ILogger logger) + { + Logger = logger; + DnsResolveTimeout = configuration.DnsResolveTimeout; + _defaultDnsTimeToLive = configuration.DefaultDnsTimeToLive; + } + + public async Task ResolveAsync(string host, CancellationToken cancellationToken) + { + DnsResponse dnsResponse = null; + if (cancellationToken.IsCancellationRequested) + { + Logger.Error($"DNS resolver called with cancelled token for host '{host}'."); + } + else if (host.IsNullOrEmpty()) + { + Logger.Error($"DNS resolver called for empty host '{host}'."); + } + else + { + Logger.Info($"Attempting to resolve host '{host}'."); + dnsResponse = await StartTasksAndWaitAnySuccessAsync(host, cancellationToken); + } + LogResult(host, dnsResponse); + return dnsResponse; + } + + protected abstract Task StartTasksAndWaitAnySuccessAsync(string host, CancellationToken cancellationToken); + + private void LogResult(string host, DnsResponse dnsResponse) + { + if (IsNullOrEmpty(dnsResponse)) + { + Logger.Error($"Failed to resolve host '{host}'."); + } + else + { + Logger.Info($"Successfully resolved host '{host}'."); + } + } + + protected abstract bool IsNullOrEmpty(DnsResponse dnsResponse); + + // This method receives the DNS request tasks as funcs to be able to use with them a cancellation token it can cancel. + // When there is a task that successfully responds, this method can cancel immediately all other pending tasks. + // When there is a timeout or an unexpected error, this method can cancel immediately all pending tasks. + protected async Task WaitAnySuccessfulResolveAsync( + IList>> resolveFuncs, TimeSpan timeout, CancellationToken cancellationToken) + { + CancellationTokenSource timeoutCancellationTokenSource = new(timeout); + CancellationTokenSource childCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, timeoutCancellationTokenSource.Token); + IList> resolveTasks = resolveFuncs + .Select(resolveTask => resolveTask(childCancellationTokenSource.Token)).ToList(); + + return await TryWaitAnySuccessfulResolveAsync(resolveTasks, childCancellationTokenSource); + } + + private async Task TryWaitAnySuccessfulResolveAsync(IList> resolveTasks, + CancellationTokenSource cancellationTokenSource) + { + DnsResponse dnsResponse = null; + while (resolveTasks.Any()) + { + try + { + Task completedTask = await Task.WhenAny(resolveTasks); + resolveTasks.Remove(completedTask); + dnsResponse = await completedTask; + + if (!IsNullOrEmpty(dnsResponse)) + { + break; + } + if (cancellationTokenSource.Token.IsCancellationRequested) + { + LogOperationCancelled("Task cancelled after a DNS resolve task completed without response."); + break; + } + } + catch (Exception e) + { + if (e.IsOrAnyInnerIsOfExceptionType()) + { + LogOperationCancelled("Task cancelled when waiting for DNS resolve tasks."); + } + else + { + Logger.Error("Unexpected error in DNS resolve task wait.", e); + } + break; + } + } + + resolveTasks.ForEach(t => t.IgnoreExceptions()); + cancellationTokenSource.Cancel(); + return dnsResponse; + } + + protected void LogOperationCancelled(string message, + [CallerFilePath] string sourceFilePath = "", + [CallerMemberName] string sourceMemberName = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + Logger.Info(message, + sourceFilePath: sourceFilePath, + sourceMemberName: sourceMemberName, + sourceLineNumber: sourceLineNumber); + } + + protected DnsResponse CreateDnsResponseWithIpAddresses(string host, int? timeToLiveInSeconds, + IList systemTypeIpAddresses) + { + if (systemTypeIpAddresses.IsNullOrEmpty()) + { + Logger.Error("Cannot create DNS response entity because no IP addresses were provided."); + return null; + } + IList ipAddresses = systemTypeIpAddresses.Select(ia => new IpAddress(ia)).ToList(); + DnsResponse dnsResponse = new(host, GetTimeToLiveOrDefault(timeToLiveInSeconds), ipAddresses); + Logger.Info($"Created DNS response entity for host '{dnsResponse.Host}' with " + + $"{dnsResponse.IpAddresses.Count} IP addresses and an expiration date in UTC of " + + $"{dnsResponse.ExpirationDateTimeUtc} based on a TTL of {dnsResponse.TimeToLive}."); + Logger.Debug($"IP addresses: [{string.Join(",", dnsResponse.IpAddresses.Select(ia => ia.ToString()))}]."); + + return dnsResponse; + } + + protected TimeSpan GetTimeToLiveOrDefault(int? timeToLiveInSeconds) + { + return timeToLiveInSeconds is null or 0 + ? _defaultDnsTimeToLive.RandomizedWithDeviation(0.25) + : TimeSpan.FromSeconds(timeToLiveInSeconds.Value); + } + + protected DnsResponse CreateDnsResponseWithAlternativeHosts(string host, int? timeToLiveInSeconds, + IList alternativeHosts) + { + if (alternativeHosts.IsNullOrEmpty()) + { + Logger.Error("Cannot create DNS response entity because no alternative hosts were provided."); + return null; + } + DnsResponse dnsResponse = new(host, GetTimeToLiveOrDefault(timeToLiveInSeconds), alternativeHosts); + Logger.Info($"Created DNS response entity for host '{dnsResponse.Host}' with " + + $"{dnsResponse.AlternativeHosts.Count} alternative hosts and an expiration date in UTC of " + + $"{dnsResponse.ExpirationDateTimeUtc} based on a TTL of {dnsResponse.TimeToLive}."); + Logger.Debug( + $"Alternative hosts: [{string.Join(",", dnsResponse.AlternativeHosts.Select(ia => ia.ToString()))}]."); + + return dnsResponse; + } + } +} \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/app.config b/src/Dns/ProtonVPN.Dns/app.config similarity index 63% rename from test/ProtonVPN.Common.Test/app.config rename to src/Dns/ProtonVPN.Dns/app.config index 3df9e48cd..f10b4ac05 100644 --- a/test/ProtonVPN.Common.Test/app.config +++ b/src/Dns/ProtonVPN.Dns/app.config @@ -6,10 +6,6 @@ - - - - \ No newline at end of file diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs index 581543462..6f52888f5 100644 --- a/src/GlobalAssemblyInfo.cs +++ b/src/GlobalAssemblyInfo.cs @@ -9,11 +9,11 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("ProtonVPN")] [assembly: AssemblyProduct("Proton VPN")] -[assembly: AssemblyCopyright("Copyright © 2022 Proton AG")] +[assembly: AssemblyCopyright("Copyright © 2022 Proton AG")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2.0.6.0")] -[assembly: AssemblyFileVersion("2.0.6.0")] +[assembly: AssemblyVersion("2.1.1.0")] +[assembly: AssemblyFileVersion("2.1.1.0")] [assembly: ComVisible(false)] [assembly: AssemblyInformationalVersion("$AssemblyVersion")] \ No newline at end of file diff --git a/src/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs b/src/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs new file mode 100644 index 000000000..6c0f48de3 --- /dev/null +++ b/src/ProtonVPN.Api.Contracts/Exceptions/AlternativeRoutingException.cs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System; + +namespace ProtonVPN.Api.Contracts.Exceptions +{ + public class AlternativeRoutingException : Exception + { + public AlternativeRoutingException() : base() + { + } + + public AlternativeRoutingException(string message) : base(message) + { + } + + public AlternativeRoutingException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs new file mode 100644 index 000000000..3cd776371 --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfCancellingHandler.cs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using ProtonVPN.Api.Handlers; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfCancellingHandler : CancellingHandlerBase + { + public MockOfCancellingHandler(MockHttpMessageHandler mockHttpMessageHandler) + { + InnerHandler = mockHttpMessageHandler; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs new file mode 100644 index 000000000..adf8bfbcf --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfDelegatingHandler.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Net.Http; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfDelegatingHandler : DelegatingHandler + { + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs new file mode 100644 index 000000000..99ec1ab60 --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfHumanVerificationHandler.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using ProtonVPN.Api.Handlers; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfHumanVerificationHandler : HumanVerificationHandlerBase + { + public MockOfHumanVerificationHandler() + { + } + + public MockOfHumanVerificationHandler(MockHttpMessageHandler mockHttpMessageHandler) + { + InnerHandler = mockHttpMessageHandler; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs new file mode 100644 index 000000000..f771aa029 --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfLoggingHandler.cs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using ProtonVPN.Api.Handlers; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfLoggingHandler : LoggingHandlerBase + { + public MockOfLoggingHandler(MockHttpMessageHandler mockHttpMessageHandler) + { + InnerHandler = mockHttpMessageHandler; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs new file mode 100644 index 000000000..187ef07fd --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfRetryingHandler.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; +using ProtonVPN.Api.Contracts.Common; +using ProtonVPN.Api.Handlers.Retries; +using RichardSzalay.MockHttp; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfRetryingHandler : RetryingHandlerBase + { + private readonly MockHttpMessageHandler _fakeHttpMessageHandler = new(); + + public MockOfRetryingHandler() + { + InnerHandler = _fakeHttpMessageHandler; + } + + public void SetResponseAsSuccess(int code) + { + _fakeHttpMessageHandler.When("*").Respond(req => new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonConvert.SerializeObject(new BaseResponse { Code = code })) + }); + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs b/src/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs new file mode 100644 index 000000000..51601d734 --- /dev/null +++ b/src/ProtonVPN.Api.Tests/Mocks/MockOfWebRequestHandler.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System.Net.Http; + +namespace ProtonVPN.Api.Tests.Mocks +{ + public class MockOfWebRequestHandler : WebRequestHandler + { + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs b/src/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs new file mode 100644 index 000000000..10bec1b7c --- /dev/null +++ b/src/ProtonVPN.Api/Handlers/StackBuilders/CompleteHttpMessageHandlerStackBuilder.cs @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; + +namespace ProtonVPN.Api.Handlers.StackBuilders +{ + public class CompleteHttpMessageHandlerStackBuilder + { + private readonly HttpMessageHandler _httpMessageHandler; + private readonly List _delegatingHandlers; + + public CompleteHttpMessageHandlerStackBuilder(HttpMessageHandler httpMessageHandler, + List delegatingHandlers = null) + { + _httpMessageHandler = httpMessageHandler ?? throw new ArgumentNullException(nameof(httpMessageHandler)); + _delegatingHandlers = delegatingHandlers ?? new(); + } + + public HttpMessageHandler Build() + { + List delegatingHandlers = _delegatingHandlers.ToList(); + delegatingHandlers.Reverse(); + HttpMessageHandler innerHttpMessageHandler = _httpMessageHandler; + + foreach (DelegatingHandler delegatingHandler in delegatingHandlers) + { + delegatingHandler.InnerHandler = innerHttpMessageHandler; + innerHttpMessageHandler = delegatingHandler; + } + + return innerHttpMessageHandler; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs b/src/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs new file mode 100644 index 000000000..e1cf34370 --- /dev/null +++ b/src/ProtonVPN.Api/Handlers/StackBuilders/HttpMessageHandlerStackBuilder.cs @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace ProtonVPN.Api.Handlers.StackBuilders +{ + public class HttpMessageHandlerStackBuilder + { + private List _delegatingHandlers = new(); + + public HttpMessageHandlerStackBuilder AddDelegatingHandler(DelegatingHandler delegatingHandler) + { + _delegatingHandlers.Add(delegatingHandler); + return this; + } + + public CompleteHttpMessageHandlerStackBuilder AddLastHandler(HttpMessageHandler httpMessageHandler) + { + if (httpMessageHandler is null) + { + throw new ArgumentNullException(nameof(httpMessageHandler)); + } + if (httpMessageHandler is DelegatingHandler) + { + throw new ArgumentException($"The last handler cannot be of {nameof(DelegatingHandler)} " + + $"type as it would not have an InnerHandler defined."); + } + return new CompleteHttpMessageHandlerStackBuilder(httpMessageHandler, _delegatingHandlers); + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.App/Account/AccountModalViewModel.cs b/src/ProtonVPN.App/Account/AccountModalViewModel.cs index 93424a020..dc1fbf990 100644 --- a/src/ProtonVPN.App/Account/AccountModalViewModel.cs +++ b/src/ProtonVPN.App/Account/AccountModalViewModel.cs @@ -26,7 +26,7 @@ using ProtonVPN.Core.Servers; using ProtonVPN.Core.Servers.Specs; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Modals; using ProtonVPN.Translations; @@ -52,7 +52,7 @@ public string ActionMessage public PromoCodeViewModel PromoCodeViewModel { get; } - public bool IsToShowUseCoupon => _appSettings.FeaturePromoCodeEnabled && _userStorage.User().CanUsePromoCode(); + public bool IsToShowUseCoupon => _appSettings.FeaturePromoCodeEnabled && _userStorage.GetUser().CanUsePromoCode(); public AccountModalViewModel( IAppSettings appSettings, @@ -119,8 +119,8 @@ public string PlusPlanCountries } } - public bool IsFreePlan => !_userStorage.User().Paid(); - public bool IsPlusPlan => _userStorage.User().IsPlusPlan(); + public bool IsFreePlan => !_userStorage.GetUser().Paid(); + public bool IsPlusPlan => _userStorage.GetUser().IsPlusPlan(); public string Username { @@ -164,7 +164,7 @@ public void Handle(AccountActionMessage message) private void SetUserDetails() { - User user = _userStorage.User(); + User user = _userStorage.GetUser(); PlanName = user.VpnPlanName.IsNullOrEmpty() ? Translation.Get("Account_lbl_Free") : user.VpnPlanName; Username = user.Username; AccountType = user.GetAccountPlan(); diff --git a/src/ProtonVPN.App/Account/PromoCodeManager.cs b/src/ProtonVPN.App/Account/PromoCodeManager.cs index 515f4ff9c..c69f03fca 100644 --- a/src/ProtonVPN.App/Account/PromoCodeManager.cs +++ b/src/ProtonVPN.App/Account/PromoCodeManager.cs @@ -18,7 +18,6 @@ */ using System; -using System.Net.Http; using System.Threading.Tasks; using Polly.Timeout; using ProtonVPN.Api.Contracts; @@ -49,7 +48,7 @@ public async Task ApplyPromoCodeAsync(string code) { return Result.Fail(Translation.Get("Api_error_Timeout")); } - catch (HttpRequestException e) + catch (Exception e) { return Result.Fail(e.Message); } diff --git a/src/ProtonVPN.App/Account/VpnInfoUpdater.cs b/src/ProtonVPN.App/Account/VpnInfoUpdater.cs index d769f0522..3f07a411c 100644 --- a/src/ProtonVPN.App/Account/VpnInfoUpdater.cs +++ b/src/ProtonVPN.App/Account/VpnInfoUpdater.cs @@ -25,37 +25,43 @@ using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Auth; using ProtonVPN.Common.Extensions; +using ProtonVPN.Common.Logging; +using ProtonVPN.Common.Logging.Categorization.Events.AppLogs; using ProtonVPN.Common.Threading; +using ProtonVPN.Core.Auth; using ProtonVPN.Core.Settings; using ProtonVPN.Core.Windows; namespace ProtonVPN.Account { - public class VpnInfoUpdater : IHandle, IVpnInfoUpdater + public class VpnInfoUpdater : IHandle, IVpnInfoUpdater, ILoggedInAware, ILogoutAware { - private readonly TimeSpan _checkInterval; private readonly IApiClient _api; + private readonly ILogger _logger; private readonly IUserStorage _userStorage; - private static readonly SemaphoreSlim Semaphore = new(1, 1); - private DateTime _lastCheck = DateTime.Now; + + private readonly TimeSpan _checkInterval; private readonly ISchedulerTimer _timer; + private DateTime _lastCheck = DateTime.Now; + private readonly SemaphoreSlim _semaphore = new(1, 1); - public VpnInfoUpdater(Common.Configuration.Config appConfig, - IEventAggregator eventAggregator, - IApiClient api, + public VpnInfoUpdater(IApiClient api, + ILogger logger, IUserStorage userStorage, + Common.Configuration.Config appConfig, + IEventAggregator eventAggregator, IScheduler scheduler) { eventAggregator.Subscribe(this); - _checkInterval = appConfig.VpnInfoCheckInterval.RandomizedWithDeviation(0.2); _api = api; + _logger = logger; _userStorage = userStorage; + _checkInterval = appConfig.VpnInfoCheckInterval.RandomizedWithDeviation(0.2); _timer = scheduler.Timer(); _timer.Interval = appConfig.ServerUpdateInterval.RandomizedWithDeviation(0.2); _timer.Tick += OnTimerTick; - _timer.Start(); } private async void OnTimerTick(object sender, EventArgs e) @@ -86,7 +92,7 @@ private async Task Handle() public async Task Update() { - await Semaphore.WaitAsync(); + await _semaphore.WaitAsync(); try { @@ -96,13 +102,27 @@ public async Task Update() _userStorage.StoreVpnInfo(response.Value); } } - catch (HttpRequestException) + catch (Exception e) { + if (e is not HttpRequestException) + { + _logger.Error("An unexpected exception was thrown when updating the VPN info.", e); + } } finally { - Semaphore.Release(); + _semaphore.Release(); } } + + public void OnUserLoggedIn() + { + _timer.Start(); + } + + public void OnUserLoggedOut() + { + _timer.Stop(); + } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/App.config b/src/ProtonVPN.App/App.config index 4f00367d2..ba7915ebc 100644 --- a/src/ProtonVPN.App/App.config +++ b/src/ProtonVPN.App/App.config @@ -394,6 +394,9 @@ False + + + diff --git a/src/ProtonVPN.App/BugReporting/Attachments/AttachmentsToApiFiles.cs b/src/ProtonVPN.App/BugReporting/Attachments/AttachmentsToApiFiles.cs index 148efd50f..78fb0f7b9 100644 --- a/src/ProtonVPN.App/BugReporting/Attachments/AttachmentsToApiFiles.cs +++ b/src/ProtonVPN.App/BugReporting/Attachments/AttachmentsToApiFiles.cs @@ -24,7 +24,7 @@ namespace ProtonVPN.BugReporting.Attachments { - internal class AttachmentsToApiFiles : IEnumerable + public class AttachmentsToApiFiles : IEnumerable { private readonly IEnumerable _source; diff --git a/src/ProtonVPN.App/BugReporting/Attachments/Source/FilesToAttachments.cs b/src/ProtonVPN.App/BugReporting/Attachments/Source/FilesToAttachments.cs index 7d98df1cb..dff190b66 100644 --- a/src/ProtonVPN.App/BugReporting/Attachments/Source/FilesToAttachments.cs +++ b/src/ProtonVPN.App/BugReporting/Attachments/Source/FilesToAttachments.cs @@ -23,7 +23,7 @@ namespace ProtonVPN.BugReporting.Attachments.Source { - internal class FilesToAttachments : IEnumerable + public class FilesToAttachments : IEnumerable { private readonly IEnumerable _source; diff --git a/src/ProtonVPN.App/BugReporting/Attachments/Source/LogFileSource.cs b/src/ProtonVPN.App/BugReporting/Attachments/Source/LogFileSource.cs index 54f917dca..bf35985e5 100644 --- a/src/ProtonVPN.App/BugReporting/Attachments/Source/LogFileSource.cs +++ b/src/ProtonVPN.App/BugReporting/Attachments/Source/LogFileSource.cs @@ -24,7 +24,7 @@ namespace ProtonVPN.BugReporting.Attachments.Source { - internal class LogFileSource : IEnumerable + public class LogFileSource : IEnumerable { private readonly string _path; private readonly int _count; diff --git a/src/ProtonVPN.App/BugReporting/Attachments/Source/SafeFileSource.cs b/src/ProtonVPN.App/BugReporting/Attachments/Source/SafeFileSource.cs index 940c5c051..735d9dc2e 100644 --- a/src/ProtonVPN.App/BugReporting/Attachments/Source/SafeFileSource.cs +++ b/src/ProtonVPN.App/BugReporting/Attachments/Source/SafeFileSource.cs @@ -28,7 +28,7 @@ namespace ProtonVPN.BugReporting.Attachments.Source { - internal class SafeFileSource : IEnumerable + public class SafeFileSource : IEnumerable { private readonly ILogger _logger; private readonly IEnumerable _origin; diff --git a/src/ProtonVPN.App/BugReporting/BugReport.cs b/src/ProtonVPN.App/BugReporting/BugReport.cs index 0430ebea6..7797df4df 100644 --- a/src/ProtonVPN.App/BugReporting/BugReport.cs +++ b/src/ProtonVPN.App/BugReporting/BugReport.cs @@ -67,7 +67,7 @@ private async Task SendInternalAsync(KeyValuePair[] fiel { return await _apiClient.ReportBugAsync(fields, files); } - catch (Exception e) when (e is HttpRequestException || e.IsFileAccessException()) + catch (Exception e) { return Result.Fail(e.Message); } diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/DriverInstallLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/DriverInstallLog.cs index f1f0eac8c..f44b84c13 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/DriverInstallLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/DriverInstallLog.cs @@ -23,7 +23,7 @@ namespace ProtonVPN.BugReporting.Diagnostic { - internal class DriverInstallLog : BaseLog + public class DriverInstallLog : BaseLog { private const string SetupApiLogFile = "setupapi.dev.log"; diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/InstalledAppsLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/InstalledAppsLog.cs index 644ea5d31..b0417fbf4 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/InstalledAppsLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/InstalledAppsLog.cs @@ -27,7 +27,7 @@ namespace ProtonVPN.BugReporting.Diagnostic { - internal class InstalledAppsLog : BaseLog + public class InstalledAppsLog : BaseLog { private const string UninstallRegistryPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; private const string WowUninstallRegistryPath = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs index 7f537bf6c..b458e9670 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs @@ -23,7 +23,7 @@ namespace ProtonVPN.BugReporting.Diagnostic { - internal class NetworkAdapterLog : BaseLog + public class NetworkAdapterLog : BaseLog { private readonly INetworkInterfaces _networkInterfaces; diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/RoutingTableLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/RoutingTableLog.cs index f08ef3629..6831b120a 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/RoutingTableLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/RoutingTableLog.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.BugReporting.Diagnostic { - internal class RoutingTableLog : BaseLog + public class RoutingTableLog : BaseLog { private readonly IOsProcesses _osProcesses; diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs index 5f00dfc1a..46a42501f 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs @@ -28,7 +28,7 @@ namespace ProtonVPN.BugReporting.Diagnostic { - internal class UserSettingsLog : BaseLog, ILoggedInAware, ILogoutAware + public class UserSettingsLog : BaseLog, ILoggedInAware, ILogoutAware { private readonly IAppSettings _appSettings; private bool _isUserLoggedIn; diff --git a/src/ProtonVPN.App/BugReporting/EmailValidator.cs b/src/ProtonVPN.App/BugReporting/EmailValidator.cs index 71d1ecf37..e055d58af 100644 --- a/src/ProtonVPN.App/BugReporting/EmailValidator.cs +++ b/src/ProtonVPN.App/BugReporting/EmailValidator.cs @@ -23,7 +23,7 @@ namespace ProtonVPN.BugReporting { - internal class EmailValidator + public class EmailValidator { public static bool IsValid(string email) { diff --git a/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs b/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs index 8378a9c21..cb7a867da 100644 --- a/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs +++ b/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs @@ -28,14 +28,12 @@ using ProtonVPN.Core.Models; using ProtonVPN.Core.OS; using ProtonVPN.Core.Settings; +using ProtonVPN.Core.Users; using ProtonVPN.Servers; -using UserLocation = ProtonVPN.Core.User.UserLocation; namespace ProtonVPN.BugReporting { - public class ReportFieldProvider : IReportFieldProvider, - ILoggedInAware, - ILogoutAware + public class ReportFieldProvider : IReportFieldProvider, ILoggedInAware, ILogoutAware { private const string NO_USERNAME_FIELD_VALUE = "Not provided"; @@ -57,8 +55,8 @@ public ReportFieldProvider(IUserStorage userStorage, Common.Configuration.Config public KeyValuePair[] GetFields(SendReportAction message) { - User user = _isLoggedIn ? _userStorage.User() : null; - UserLocation location = _userStorage.Location(); + User user = _isLoggedIn ? _userStorage.GetUser() : null; + UserLocation location = _userStorage.GetLocation(); string country = Countries.GetName(location.Country); string isp = location.Isp; diff --git a/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs b/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs index b7fb7df93..a689d47c6 100644 --- a/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs +++ b/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs @@ -29,7 +29,7 @@ using ProtonVPN.Core.Auth; using ProtonVPN.Core.Models; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; namespace ProtonVPN.BugReporting.Steps { @@ -188,7 +188,7 @@ public void OnUserDataChanged() private void UpdateEmailInput() { - User user = _userStorage.User(); + User user = _userStorage.GetUser(); if (EmailValidator.IsValid(user.Username)) { FormElement emailField = FormElements.GetEmailField(); diff --git a/src/ProtonVPN.App/Config/ConfigDirectories.cs b/src/ProtonVPN.App/Config/ConfigDirectories.cs index 77c7bbf09..9659cdb25 100644 --- a/src/ProtonVPN.App/Config/ConfigDirectories.cs +++ b/src/ProtonVPN.App/Config/ConfigDirectories.cs @@ -21,7 +21,7 @@ namespace ProtonVPN.Config { - internal class ConfigDirectories + public class ConfigDirectories { private readonly Common.Configuration.Config _config; diff --git a/src/ProtonVPN.App/Config/Url/ActiveUrl.cs b/src/ProtonVPN.App/Config/Url/ActiveUrl.cs index 8081dd3d8..1a2730b90 100644 --- a/src/ProtonVPN.App/Config/Url/ActiveUrl.cs +++ b/src/ProtonVPN.App/Config/Url/ActiveUrl.cs @@ -25,7 +25,7 @@ namespace ProtonVPN.Config.Url { - internal class ActiveUrl : IActiveUrl + public class ActiveUrl : IActiveUrl { private readonly IOsProcesses _processes; private string _url; diff --git a/src/ProtonVPN.App/Config/Url/ActiveUrls.cs b/src/ProtonVPN.App/Config/Url/ActiveUrls.cs index 923d228e7..6da272dd9 100644 --- a/src/ProtonVPN.App/Config/Url/ActiveUrls.cs +++ b/src/ProtonVPN.App/Config/Url/ActiveUrls.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.Config.Url { - internal class ActiveUrls : IActiveUrls + public class ActiveUrls : IActiveUrls { private readonly UrlConfig _config; private readonly IOsProcesses _processes; diff --git a/src/ProtonVPN.App/ConnectionInfo/ConnectionErrorResolver.cs b/src/ProtonVPN.App/ConnectionInfo/ConnectionErrorResolver.cs index 192caf123..1da3430d1 100644 --- a/src/ProtonVPN.App/ConnectionInfo/ConnectionErrorResolver.cs +++ b/src/ProtonVPN.App/ConnectionInfo/ConnectionErrorResolver.cs @@ -57,7 +57,7 @@ public ConnectionErrorResolver( public async Task ResolveError() { - User oldUserInfo = _userStorage.User(); + User oldUserInfo = _userStorage.GetUser(); if (oldUserInfo.IsDelinquent()) { return VpnError.Unpaid; @@ -70,7 +70,7 @@ public async Task ResolveError() } await _vpnInfoUpdater.Update(); - User newUserInfo = _userStorage.User(); + User newUserInfo = _userStorage.GetUser(); if (newUserInfo.MaxTier < oldUserInfo.MaxTier) { @@ -110,7 +110,7 @@ private async Task GetSessionCount() return response.Value.Sessions.Count; } } - catch (HttpRequestException) + catch { } diff --git a/src/ProtonVPN.App/Core/AppSettings.cs b/src/ProtonVPN.App/Core/AppSettings.cs index df701581d..ccea155cc 100644 --- a/src/ProtonVPN.App/Core/AppSettings.cs +++ b/src/ProtonVPN.App/Core/AppSettings.cs @@ -18,6 +18,7 @@ */ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; @@ -39,6 +40,7 @@ using ProtonVPN.Core.Settings; using ProtonVPN.Core.Settings.Contracts; using ProtonVPN.Core.Storage; +using ProtonVPN.Dns.Contracts; using ProtonVPN.Settings; using Announcement = ProtonVPN.Core.Announcements.Announcement; @@ -493,20 +495,20 @@ public bool AllowNonStandardPorts public string AuthenticationPublicKey { - get => GetPerUser()?.Decrypt(); - set => SetPerUser(value?.Encrypt()); + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); } public string AuthenticationSecretKey { - get => GetPerUser()?.Decrypt(); - set => SetPerUser(value?.Encrypt()); + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); } public string AuthenticationCertificatePem { - get => GetPerUser()?.Decrypt(); - set => SetPerUser(value?.Encrypt()); + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); } public DateTimeOffset? AuthenticationCertificateExpirationUtcDate @@ -529,8 +531,26 @@ public DateTimeOffset? AuthenticationCertificateRequestUtcDate public string CertificationServerPublicKey { - get => GetPerUser()?.Decrypt(); - set => SetPerUser(value?.Encrypt()); + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); + } + + public string AccessToken + { + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); + } + + public string RefreshToken + { + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); + } + + public string Uid + { + get => GetPerUserDecrypted(); + set => SetPerUserEncrypted(value); } public bool HardwareAccelerationEnabled @@ -545,6 +565,12 @@ public bool IsToShowRebrandingPopup set => Set(value); } + public ConcurrentDictionary DnsCache + { + get => Get>(); + set => Set(value); + } + public TimeSpan MaintenanceCheckInterval { get @@ -648,8 +674,8 @@ private void Set(T value, [CallerMemberName] string propertyName = null) private void LogChange(string propertyName, T oldValue, T newValue) { - string oldValueJson = JsonConvert.SerializeObject(oldValue).LimitLength(64); - string newValueJson = JsonConvert.SerializeObject(newValue).LimitLength(64); + string oldValueJson = JsonConvert.SerializeObject(oldValue).GetLastChars(64); + string newValueJson = JsonConvert.SerializeObject(newValue).GetLastChars(64); _logger.Info($"Setting '{propertyName}' " + $"changed from '{oldValueJson}' to '{newValueJson}'."); } @@ -661,7 +687,14 @@ private T GetPerUser([CallerMemberName] string propertyName = null) return _userSettings.Get(propertyName); } - private void SetPerUser(T value, [CallerMemberName] string propertyName = null) + private string GetPerUserDecrypted([CallerMemberName] string propertyName = null) + { + _accessedPerUserProperties.Add(propertyName); + + return _userSettings.Get(propertyName)?.Decrypt(); + } + + private void SetPerUserInner(T value, string propertyName) { _accessedPerUserProperties.Add(propertyName); @@ -681,6 +714,16 @@ private void SetPerUser(T value, [CallerMemberName] string propertyName = nul LogChange(propertyName, oldValue, value); } + private void SetPerUser(T value, [CallerMemberName] string propertyName = null) + { + SetPerUserInner(value, propertyName); + } + + private void SetPerUserEncrypted(string value, [CallerMemberName] string propertyName = null) + { + SetPerUserInner(value?.Encrypt(), propertyName); + } + private Type UnwrapNullable(Type type) { return IsNullableType(type) ? Nullable.GetUnderlyingType(type) : type; diff --git a/src/ProtonVPN.App/Core/Bootstraper.cs b/src/ProtonVPN.App/Core/Bootstraper.cs index 04bec03fc..6f1811263 100644 --- a/src/ProtonVPN.App/Core/Bootstraper.cs +++ b/src/ProtonVPN.App/Core/Bootstraper.cs @@ -64,8 +64,9 @@ using ProtonVPN.Core.Settings; using ProtonVPN.Core.Startup; using ProtonVPN.Core.Update; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Core.Vpn; +using ProtonVPN.Dns.Installers; using ProtonVPN.ErrorHandling; using ProtonVPN.HumanVerification.Installers; using ProtonVPN.Login; @@ -120,7 +121,8 @@ protected override void Configure() .RegisterModule() .RegisterModule() .RegisterAssemblyModules(typeof(HumanVerificationModule).Assembly) - .RegisterAssemblyModules(typeof(ApiModule).Assembly); + .RegisterAssemblyModules(typeof(ApiModule).Assembly) + .RegisterAssemblyModules(typeof(DnsModule).Assembly); new ProtonVPN.Update.Config.Module().Load(builder); @@ -159,7 +161,7 @@ protected override async void OnStartup(object sender, StartupEventArgs e) await Resolve().FetchData(); await StartVpnService(); - if (Resolve().User().Empty() || !await IsUserValid() || await SessionExpired()) + if (Resolve().GetUser().Empty() || !await IsUserValid() || await SessionExpired()) { ShowLoginForm(); return; @@ -177,7 +179,7 @@ public void OnExit() private async Task SessionExpired() { - if (string.IsNullOrEmpty(Resolve().AccessToken)) + if (string.IsNullOrEmpty(Resolve().AccessToken)) { return true; } @@ -187,7 +189,7 @@ private async Task SessionExpired() ApiResponseResult result = await Resolve().RefreshVpnInfoAsync(); return result.Failure; } - catch (HttpRequestException) + catch { return false; } @@ -225,7 +227,7 @@ private async Task IsUserValid() return false; } } - catch (HttpRequestException ex) + catch (Exception ex) { loginViewModel.HandleAuthFailure(AuthResult.Fail(ex.Message)); ShowLoginForm(); @@ -492,7 +494,7 @@ private void CheckAuthenticationServerStatus() { Resolve().CheckAuthenticationServerStatusAsync().IgnoreExceptions(); } - catch (HttpRequestException e) + catch (Exception e) { Resolve().Error("The app failed to check auth server status.", e); } diff --git a/src/ProtonVPN.App/Core/Ioc/AppModule.cs b/src/ProtonVPN.App/Core/Ioc/AppModule.cs index b9583becf..89aadfe17 100644 --- a/src/ProtonVPN.App/Core/Ioc/AppModule.cs +++ b/src/ProtonVPN.App/Core/Ioc/AppModule.cs @@ -18,13 +18,11 @@ */ using System; -using System.Windows; using Autofac; using Caliburn.Micro; using ProtonVPN.About; using ProtonVPN.Account; using ProtonVPN.Api; -using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Servers; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Events; @@ -47,7 +45,7 @@ using ProtonVPN.Core.Settings; using ProtonVPN.Core.Startup; using ProtonVPN.Core.Storage; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Core.Vpn; using ProtonVPN.Core.Windows; using ProtonVPN.Core.Windows.Popups; @@ -84,7 +82,7 @@ protected override void Load(ContainerBuilder builder) { base.Load(builder); - builder.Register(c => new ConfigFactory().Config()); + builder.Register(c => new ConfigFactory().Config()).AsSelf().As().SingleInstance(); // REMOVE AS SELF builder.RegisterType().As().SingleInstance(); builder.RegisterType().SingleInstance(); @@ -287,12 +285,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); - builder.Register(c => new VpnInfoUpdater( - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve())).As().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/ProtonVPN.App/Core/Ioc/CoreModule.cs b/src/ProtonVPN.App/Core/Ioc/CoreModule.cs index aa8c20a19..6c0347db9 100644 --- a/src/ProtonVPN.App/Core/Ioc/CoreModule.cs +++ b/src/ProtonVPN.App/Core/Ioc/CoreModule.cs @@ -34,7 +34,6 @@ using ProtonVPN.Common.OS.Services; using ProtonVPN.Common.Threading; using ProtonVPN.Config.Url; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Announcements; using ProtonVPN.Core.Auth; using ProtonVPN.Core.Config; @@ -48,7 +47,6 @@ using ProtonVPN.Core.Servers; using ProtonVPN.Core.Service; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.Storage; using ProtonVPN.Core.Threading; using ProtonVPN.Core.Update; using ProtonVPN.Core.Vpn; @@ -56,8 +54,8 @@ using ProtonVPN.HumanVerification; using ProtonVPN.HumanVerification.Contracts; using ProtonVPN.Modals.ApiActions; -using ProtonVPN.Settings; using ProtonVPN.Vpn; +using CoreDnsClient = ProtonVPN.Core.OS.Net.Dns.DnsClient; using Module = Autofac.Module; namespace ProtonVPN.Core.Ioc @@ -80,21 +78,10 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.Register(c => Schedulers.FromApplicationDispatcher()).As().SingleInstance(); - builder.Register(c => new TokenStorage(c.Resolve())).As().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); - - builder.Register(c => - new TokenClient( - c.Resolve(), - new HttpClient(c.Resolve()) - { BaseAddress = c.Resolve().ApiUrl.Uri }, - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve())) - .As() - .SingleInstance(); + + builder.RegisterType().As().SingleInstance(); builder.RegisterType().SingleInstance(); @@ -110,14 +97,14 @@ protected override void Load(ContainerBuilder builder) c.Resolve(), c.Resolve(), c.Resolve(), - c.Resolve(), + c.Resolve(), c.Resolve())).SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.Register(c => new SafeDnsClient( - new DnsClient( + new CoreDnsClient( c.Resolve(), c.Resolve()))) .As().SingleInstance(); @@ -142,7 +129,6 @@ protected override void Load(ContainerBuilder builder) builder.Register(c => new SafeSystemProxy(c.Resolve(), new SystemProxy())) .AsImplementedInterfaces() .SingleInstance(); - builder.Register(c => new MainHostname(c.Resolve().Urls.ApiUrl)).SingleInstance(); builder.Register(c => new DohClients( c.Resolve().DoHProviders, c.Resolve().DohClientTimeout)) diff --git a/src/ProtonVPN.App/Core/Ioc/UpdateModule.cs b/src/ProtonVPN.App/Core/Ioc/UpdateModule.cs index 2fa8d58e3..a479b47ad 100644 --- a/src/ProtonVPN.App/Core/Ioc/UpdateModule.cs +++ b/src/ProtonVPN.App/Core/Ioc/UpdateModule.cs @@ -18,10 +18,13 @@ */ using System; +using System.Net.Http; using Autofac; using ProtonVPN.Api; using ProtonVPN.Api.Handlers; using ProtonVPN.Api.Handlers.Retries; +using ProtonVPN.Api.Handlers.StackBuilders; +using ProtonVPN.Api.Handlers.TlsPinning; using ProtonVPN.Common.OS.Net.Http; using ProtonVPN.Core.Update; using ProtonVPN.Update.Config; @@ -36,18 +39,28 @@ protected override void Load(ContainerBuilder builder) base.Load(builder); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); - builder.Register(c => new DefaultAppUpdateConfig - { - HttpClient = - c.Resolve().Client(c.Resolve(), - c.Resolve().UserAgent()), - FeedUriProvider = c.Resolve(), - UpdatesPath = c.Resolve().UpdatesPath, - CurrentVersion = Version.Parse(c.Resolve().AppVersion), - EarlyAccessCategoryName = "EarlyAccess", - MinProgressDuration = TimeSpan.FromSeconds(1.5) - }) - .As().SingleInstance(); + builder.Register(CreateDefaultAppUpdateConfig).As().SingleInstance(); + } + + // TO DO: Refactor the code in order to delete this custom registration + private DefaultAppUpdateConfig CreateDefaultAppUpdateConfig(IComponentContext c) + { + HttpMessageHandler innerHandler = new HttpMessageHandlerStackBuilder() + .AddDelegatingHandler(c.Resolve()) + .AddDelegatingHandler(c.Resolve()) + .AddDelegatingHandler(c.Resolve()) + .AddLastHandler(c.Resolve()) + .Build(); + + return new DefaultAppUpdateConfig + { + HttpClient = c.Resolve().Client(innerHandler, c.Resolve().UserAgent()), + FeedUriProvider = c.Resolve(), + UpdatesPath = c.Resolve().UpdatesPath, + CurrentVersion = Version.Parse(c.Resolve().AppVersion), + EarlyAccessCategoryName = "EarlyAccess", + MinProgressDuration = TimeSpan.FromSeconds(1.5) + }; } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/Core/MVVM/Converters/BytesToSizeConverter.cs b/src/ProtonVPN.App/Core/MVVM/Converters/BytesToSizeConverter.cs index 752b6af7f..e53a70c2b 100644 --- a/src/ProtonVPN.App/Core/MVVM/Converters/BytesToSizeConverter.cs +++ b/src/ProtonVPN.App/Core/MVVM/Converters/BytesToSizeConverter.cs @@ -24,14 +24,14 @@ namespace ProtonVPN.Core.MVVM.Converters { - internal class BytesToSizeConverter : IValueConverter + public class BytesToSizeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - var bytes = ByteSize.FromBytes((double?)value ?? 0.0); - var size = bytes.LargestWholeNumberValue; + ByteSize bytes = ByteSize.FromBytes((double?)value ?? 0.0); + double size = bytes.LargestWholeNumberValue; - var format = "0"; + string format = "0"; if (bytes.Bytes >= ByteSize.BytesInKiloByte) { if (size < 10.0) diff --git a/src/ProtonVPN.App/Core/Service/Settings/SettingsContractProvider.cs b/src/ProtonVPN.App/Core/Service/Settings/SettingsContractProvider.cs index 9622b442f..28cc4cb1a 100644 --- a/src/ProtonVPN.App/Core/Service/Settings/SettingsContractProvider.cs +++ b/src/ProtonVPN.App/Core/Service/Settings/SettingsContractProvider.cs @@ -47,7 +47,7 @@ public SettingsContract GetSettingsContract(OpenVpnAdapter? openVpnAdapter = nul AppPaths = _appSettings.GetSplitTunnelApps(), Ips = GetSplitTunnelIps() }, - ModerateNat = !_userStorage.User().Empty() && _appSettings.ModerateNat, + ModerateNat = !_userStorage.GetUser().Empty() && _appSettings.ModerateNat, NetShieldMode = _appSettings.IsNetShieldEnabled() ? _appSettings.NetShieldMode : 0, SplitTcp = _appSettings.IsVpnAcceleratorEnabled(), AllowNonStandardPorts = _appSettings.ShowNonStandardPortsToFreeUsers ? _appSettings.AllowNonStandardPorts : null, diff --git a/src/ProtonVPN.App/Core/Service/Vpn/ReconnectManager.cs b/src/ProtonVPN.App/Core/Service/Vpn/ReconnectManager.cs index ef83ac8d3..46fe036c2 100644 --- a/src/ProtonVPN.App/Core/Service/Vpn/ReconnectManager.cs +++ b/src/ProtonVPN.App/Core/Service/Vpn/ReconnectManager.cs @@ -141,7 +141,7 @@ private async Task ServerOffline() return isServerUnderMaintenance; } - catch (HttpRequestException) + catch { return false; } diff --git a/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs b/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs index 56d1d17bf..305def617 100644 --- a/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs +++ b/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs @@ -188,7 +188,7 @@ public async Task ConnectToProfileAsync(Profile profile, VpnProtocol? vpnProtoco public void OnVpnStateChanged(VpnStateChangedEventArgs e) { - if (_guestHoleState.Active || _userStorage.User().Empty()) + if (_guestHoleState.Active || _userStorage.GetUser().Empty()) { return; } diff --git a/src/ProtonVPN.App/Core/Service/Vpn/VpnReconnector.cs b/src/ProtonVPN.App/Core/Service/Vpn/VpnReconnector.cs index a81b0816f..a00c8bdc7 100644 --- a/src/ProtonVPN.App/Core/Service/Vpn/VpnReconnector.cs +++ b/src/ProtonVPN.App/Core/Service/Vpn/VpnReconnector.cs @@ -56,7 +56,7 @@ public class VpnReconnector : IVpnReconnector, IVpnStateAware private readonly Common.Configuration.Config _config; private readonly Lazy _connectionStatusViewModel; private readonly ServerManager _serverManager; - private readonly Lazy _serverConnector; + private readonly IProfileFactory _profileFactory; private readonly Lazy _profileConnector; private VpnReconnectionSteps _reconnectionStep; @@ -75,7 +75,7 @@ public VpnReconnector(IAppSettings appSettings, Common.Configuration.Config config, Lazy connectionStatusViewModel, ServerManager serverManager, - Lazy serverConnector, + IProfileFactory profileFactory, Lazy profileConnector) { _appSettings = appSettings; @@ -88,7 +88,7 @@ public VpnReconnector(IAppSettings appSettings, _config = config; _connectionStatusViewModel = connectionStatusViewModel; _serverManager = serverManager; - _serverConnector = serverConnector; + _profileFactory = profileFactory; _profileConnector = profileConnector; } @@ -129,7 +129,8 @@ private async Task NonSmartReconnectAsync(Server lastServer, Profile lastProfile else if (lastServer != null) { _logger.Info("Reconnecting to last server"); - await _serverConnector.Value.Connect(lastServer); + Profile profile = _profileFactory.CreateFromServer(lastServer); + await _vpnConnector.ConnectToProfileAsync(profile); } else { diff --git a/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceActionDecorator.cs b/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceActionDecorator.cs index fc4b8baa3..5ff63f96b 100644 --- a/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceActionDecorator.cs +++ b/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceActionDecorator.cs @@ -31,7 +31,7 @@ namespace ProtonVPN.Core.Service.Vpn { - internal class VpnServiceActionDecorator : IVpnServiceManager + public class VpnServiceActionDecorator : IVpnServiceManager { private readonly ISafeServiceAction _safeServiceAction; private readonly IVpnServiceManager _decorated; diff --git a/src/ProtonVPN.App/Core/Startup/AutoStartup.cs b/src/ProtonVPN.App/Core/Startup/AutoStartup.cs index 62f3d216d..1e87ff819 100644 --- a/src/ProtonVPN.App/Core/Startup/AutoStartup.cs +++ b/src/ProtonVPN.App/Core/Startup/AutoStartup.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.Core.Startup { - internal class AutoStartup : IAutoStartup + public class AutoStartup : IAutoStartup { private readonly IStartupRecord _startupRecord; @@ -33,7 +33,7 @@ public AutoStartup(ILogger logger, Common.Configuration.Config appConfig) : this { } - internal AutoStartup(IStartupRecord startupRecord) + public AutoStartup(IStartupRecord startupRecord) { _startupRecord = startupRecord; } @@ -49,7 +49,9 @@ private void SetEnabled(bool value) if (value) { if (!_startupRecord.Exists()) + { _startupRecord.Create(); + } else if (!_startupRecord.Valid()) { _startupRecord.Remove(); @@ -59,7 +61,9 @@ private void SetEnabled(bool value) else { if (_startupRecord.Exists()) + { _startupRecord.Remove(); + } } } } diff --git a/src/ProtonVPN.App/Core/Startup/IAutoStartup.cs b/src/ProtonVPN.App/Core/Startup/IAutoStartup.cs index cbb560355..d40e1b93d 100644 --- a/src/ProtonVPN.App/Core/Startup/IAutoStartup.cs +++ b/src/ProtonVPN.App/Core/Startup/IAutoStartup.cs @@ -19,7 +19,7 @@ namespace ProtonVPN.Core.Startup { - internal interface IAutoStartup + public interface IAutoStartup { bool Enabled { get; set; } } diff --git a/src/ProtonVPN.App/Core/Startup/ISyncableAutoStartup.cs b/src/ProtonVPN.App/Core/Startup/ISyncableAutoStartup.cs index 18112dad2..eeed4be99 100644 --- a/src/ProtonVPN.App/Core/Startup/ISyncableAutoStartup.cs +++ b/src/ProtonVPN.App/Core/Startup/ISyncableAutoStartup.cs @@ -19,7 +19,7 @@ namespace ProtonVPN.Core.Startup { - internal interface ISyncableAutoStartup + public interface ISyncableAutoStartup { void Sync(); } diff --git a/src/ProtonVPN.App/Core/Startup/SyncableAutoStartup.cs b/src/ProtonVPN.App/Core/Startup/SyncableAutoStartup.cs index 637bfdfec..d5b900519 100644 --- a/src/ProtonVPN.App/Core/Startup/SyncableAutoStartup.cs +++ b/src/ProtonVPN.App/Core/Startup/SyncableAutoStartup.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.Core.Startup { - internal class SyncableAutoStartup : ISyncableAutoStartup + public class SyncableAutoStartup : ISyncableAutoStartup { private readonly IAppSettings _appSettings; private readonly IScheduler _scheduler; @@ -42,13 +42,15 @@ public SyncableAutoStartup( public void Sync() { - if (_syncing) - return; + if (!_syncing) + { + SyncForward(); - SyncForward(); - - if (SyncBackRequired()) - _scheduler.Schedule(SyncBack); + if (SyncBackRequired()) + { + _scheduler.Schedule(SyncBack); + } + } } private void SyncForward() diff --git a/src/ProtonVPN.App/Core/Startup/SyncedAutoStartup.cs b/src/ProtonVPN.App/Core/Startup/SyncedAutoStartup.cs index b1c6eb6c0..c6ad1c0f0 100644 --- a/src/ProtonVPN.App/Core/Startup/SyncedAutoStartup.cs +++ b/src/ProtonVPN.App/Core/Startup/SyncedAutoStartup.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.Core.Startup { - internal class SyncedAutoStartup : ISyncableAutoStartup, ISettingsAware + public class SyncedAutoStartup : ISyncableAutoStartup, ISettingsAware { private readonly ISyncableAutoStartup _origin; diff --git a/src/ProtonVPN.App/Core/UserLocationService.cs b/src/ProtonVPN.App/Core/UserLocationService.cs index a9bfbbd53..884ba21b0 100644 --- a/src/ProtonVPN.App/Core/UserLocationService.cs +++ b/src/ProtonVPN.App/Core/UserLocationService.cs @@ -28,7 +28,7 @@ using ProtonVPN.Common.Vpn; using ProtonVPN.Core.Auth; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Core.Vpn; namespace ProtonVPN.Core @@ -97,7 +97,7 @@ public async Task> LocationAsync() { return await _api.GetLocationDataAsync(); } - catch (HttpRequestException) + catch { return ApiResponseResult.Fail(default, ""); } @@ -122,7 +122,7 @@ private async Task UpdateAction() { _networkAddressChanged = false; - if (response.Value.Ip == _userStorage.Location().Ip) + if (response.Value.Ip == _userStorage.GetLocation().Ip) { return; } diff --git a/src/ProtonVPN.App/Login/SignUpAvailabilityProvider.cs b/src/ProtonVPN.App/Login/SignUpAvailabilityProvider.cs index 800fdb482..ef8d5bb31 100644 --- a/src/ProtonVPN.App/Login/SignUpAvailabilityProvider.cs +++ b/src/ProtonVPN.App/Login/SignUpAvailabilityProvider.cs @@ -18,7 +18,6 @@ */ using System; -using System.Net.Http; using System.Threading.Tasks; using ProtonVPN.Common.OS.Net.Http; @@ -48,7 +47,7 @@ private async Task IsApiReachable() IHttpResponseMessage response = await _httpClient.GetAsync(PROTON_API_PING_URL); return response.IsSuccessStatusCode; } - catch (Exception e) when (IsToHandleRequestException(e)) + catch { return false; } @@ -61,15 +60,10 @@ private async Task IsProtonWebSiteReachableAsync() IHttpResponseMessage response = await _httpClient.GetAsync(PROTON_WEBSITE_URL); return response.IsSuccessStatusCode; } - catch (Exception e) when (IsToHandleRequestException(e)) + catch { return false; } } - - private bool IsToHandleRequestException(Exception e) - { - return e is HttpRequestException or TimeoutException or TaskCanceledException; - } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs b/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs index 9d8dff0e4..5e1f1a38a 100644 --- a/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs +++ b/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs @@ -17,6 +17,7 @@ * along with ProtonVPN. If not, see . */ +using System; using System.ComponentModel; using System.Net.Http; using System.Security; @@ -320,7 +321,7 @@ private async void LoginAction() AuthResult loginResult = await _userAuth.LoginUserAsync(username, Password); await HandleLoginResultAsync(loginResult); } - catch (HttpRequestException ex) + catch { if (await DisableGuestHole() || _guestHoleConnector.Servers().Count == 0) { @@ -384,9 +385,9 @@ private async Task SendTwoFactorAuthRequestAsync() { return await _userAuth.SendTwoFactorCodeAsync(TwoFactorAuthCode); } - catch (HttpRequestException e) + catch (Exception ex) { - _logger.Error("Failed to send two factor auth code.", e); + _logger.Error("Failed to send two factor auth code.", ex); return AuthResult.Fail(AuthError.Unknown); } } diff --git a/src/ProtonVPN.App/Map/PinFactory.cs b/src/ProtonVPN.App/Map/PinFactory.cs index f7b0982c8..d1e75ac67 100644 --- a/src/ProtonVPN.App/Map/PinFactory.cs +++ b/src/ProtonVPN.App/Map/PinFactory.cs @@ -26,7 +26,7 @@ using ProtonVPN.Core.Servers.Name; using ProtonVPN.Core.Servers.Specs; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Core.Vpn; using ProtonVPN.Map.ViewModels.Pins; using ProtonVPN.Servers; @@ -204,7 +204,7 @@ private void SetLines() private void BuildStandardPins() { - var user = _userStorage.User(); + var user = _userStorage.GetUser(); var countries = _serverManager.GetCountries(); _pins = countries @@ -231,7 +231,7 @@ private void BuildSecureCorePins() var pin = GetExitNodePin(server.ExitCountry); if (secureCorePins.FirstOrDefault(c => c.CountryCode.Equals(server.ExitCountry)) == null) { - pin.Highlighted = _userStorage.User().MaxTier >= ServerTiers.Plus; + pin.Highlighted = _userStorage.GetUser().MaxTier >= ServerTiers.Plus; secureCorePins.Add(pin); } } diff --git a/src/ProtonVPN.App/Modals/Dialogs/Dialogs.cs b/src/ProtonVPN.App/Modals/Dialogs/Dialogs.cs index f429feaf3..e3b213090 100644 --- a/src/ProtonVPN.App/Modals/Dialogs/Dialogs.cs +++ b/src/ProtonVPN.App/Modals/Dialogs/Dialogs.cs @@ -22,7 +22,7 @@ namespace ProtonVPN.Modals.Dialogs { - internal class Dialogs : IDialogs + public class Dialogs : IDialogs { private readonly IModals _modals; private readonly QuestionModalViewModel _questionViewModel; diff --git a/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs b/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs index 230208ef2..d4ecab93f 100644 --- a/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs +++ b/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs @@ -83,7 +83,7 @@ public VpnError Error } public bool ShowUpgrade => Error == VpnError.SessionLimitReached && - _userStorage.User().MaxTier < ServerTiers.Plus; + _userStorage.GetUser().MaxTier < ServerTiers.Plus; public bool NetworkBlocked { diff --git a/src/ProtonVPN.App/Modals/Welcome/WelcomeModalManager.cs b/src/ProtonVPN.App/Modals/Welcome/WelcomeModalManager.cs index 4a989975f..b7c6f46e3 100644 --- a/src/ProtonVPN.App/Modals/Welcome/WelcomeModalManager.cs +++ b/src/ProtonVPN.App/Modals/Welcome/WelcomeModalManager.cs @@ -49,7 +49,7 @@ public WelcomeModalManager( public void Load() { - User user = _userStorage.User(); + User user = _userStorage.GetUser(); if (WelcomeModalHasToBeShown()) { ShowWelcomeModal(); @@ -58,7 +58,7 @@ public void Load() { ShowRebrandingPopup(); } - else if (!user.Paid() && !_userStorage.User().IsDelinquent()) + else if (!user.Paid() && !_userStorage.GetUser().IsDelinquent()) { ShowUpsellModal(); } diff --git a/src/ProtonVPN.App/P2PDetection/Blocked/BlockedTraffic.cs b/src/ProtonVPN.App/P2PDetection/Blocked/BlockedTraffic.cs index 1148c872a..7d26b4e92 100644 --- a/src/ProtonVPN.App/P2PDetection/Blocked/BlockedTraffic.cs +++ b/src/ProtonVPN.App/P2PDetection/Blocked/BlockedTraffic.cs @@ -32,7 +32,7 @@ namespace ProtonVPN.P2PDetection.Blocked /// P2P traffic is not allowed on free servers and some not free servers. /// All traffic gets blocked when P2P activity is detected. /// - internal class BlockedTraffic : IBlockedTraffic + public class BlockedTraffic : IBlockedTraffic { private readonly Uri _p2PStatusUri; @@ -53,18 +53,20 @@ public BlockedTraffic(IHttpClients httpClients, Uri p2PStatusUri, TimeSpan timeo public async Task Detected() { - var response = await GetResponse(); + string response = await GetResponse(); return response.Contains(""); } private async Task GetResponse() { - using (var response = await _httpClient.GetAsync(_p2PStatusUri)) + using (IHttpResponseMessage response = await _httpClient.GetAsync(_p2PStatusUri)) { if (!response.IsSuccessStatusCode) + { return string.Empty; + } - var content = await response.Content.ReadAsStringAsync(); + string content = await response.Content.ReadAsStringAsync(); return content; } } diff --git a/src/ProtonVPN.App/P2PDetection/Blocked/SafeBlockedTraffic.cs b/src/ProtonVPN.App/P2PDetection/Blocked/SafeBlockedTraffic.cs index 294e37652..2c44d6f92 100644 --- a/src/ProtonVPN.App/P2PDetection/Blocked/SafeBlockedTraffic.cs +++ b/src/ProtonVPN.App/P2PDetection/Blocked/SafeBlockedTraffic.cs @@ -17,8 +17,6 @@ * along with ProtonVPN. If not, see . */ -using System; -using System.Net.Http; using System.Threading.Tasks; using ProtonVPN.Common.Helpers; @@ -27,7 +25,7 @@ namespace ProtonVPN.P2PDetection.Blocked /// /// Suppresses exceptions specific to . /// - internal class SafeBlockedTraffic : IBlockedTraffic + public class SafeBlockedTraffic : IBlockedTraffic { private readonly IBlockedTraffic _origin; @@ -44,10 +42,9 @@ public async Task Detected() { return await _origin.Detected(); } - catch (HttpRequestException) - { } - catch (OperationCanceledException) - { } + catch + { + } return false; } diff --git a/src/ProtonVPN.App/P2PDetection/P2PDetectionTimeout.cs b/src/ProtonVPN.App/P2PDetection/P2PDetectionTimeout.cs index ecf90459b..3152568d3 100644 --- a/src/ProtonVPN.App/P2PDetection/P2PDetectionTimeout.cs +++ b/src/ProtonVPN.App/P2PDetection/P2PDetectionTimeout.cs @@ -24,7 +24,7 @@ namespace ProtonVPN.P2PDetection /// /// Calculates P2P detection timeout value from P2P detection interval. /// - internal class P2PDetectionTimeout + public class P2PDetectionTimeout { private readonly TimeSpan _interval; diff --git a/src/ProtonVPN.App/PlanDowngrading/PlanDowngradeHandler.cs b/src/ProtonVPN.App/PlanDowngrading/PlanDowngradeHandler.cs index e45e5675c..de353cf65 100644 --- a/src/ProtonVPN.App/PlanDowngrading/PlanDowngradeHandler.cs +++ b/src/ProtonVPN.App/PlanDowngrading/PlanDowngradeHandler.cs @@ -24,7 +24,7 @@ using ProtonVPN.Core.Servers.Models; using ProtonVPN.Core.Service.Vpn; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Core.Vpn; using ProtonVPN.Core.Windows.Popups; using ProtonVPN.Notifications; @@ -73,7 +73,7 @@ public async Task OnVpnPlanChangedAsync(VpnPlanChangedEventArgs e) { if (e.IsDowngrade) { - User user = _userStorage.User(); + User user = _userStorage.GetUser(); await DowngradeUserAsync(user); } } diff --git a/src/ProtonVPN.App/PortForwarding/PortForwardingManager.cs b/src/ProtonVPN.App/PortForwarding/PortForwardingManager.cs index 51a4bbc41..b5c364885 100644 --- a/src/ProtonVPN.App/PortForwarding/PortForwardingManager.cs +++ b/src/ProtonVPN.App/PortForwarding/PortForwardingManager.cs @@ -51,7 +51,7 @@ public async Task EnableAsync() { return; } - if (_userStorage.User().Paid()) + if (_userStorage.GetUser().Paid()) { await EnablePortForwardingIfConfirmedAsync(); } diff --git a/src/ProtonVPN.App/Profiles/Form/AbstractForm.cs b/src/ProtonVPN.App/Profiles/Form/AbstractForm.cs index a65e1bed1..a205eeaf3 100644 --- a/src/ProtonVPN.App/Profiles/Form/AbstractForm.cs +++ b/src/ProtonVPN.App/Profiles/Form/AbstractForm.cs @@ -341,7 +341,7 @@ protected virtual Profile CreateProfile() private bool ShowUpgradeMessageForServer(Server server) { - User user = UserStorage.User(); + User user = UserStorage.GetUser(); return user.MaxTier < server.Tier; } diff --git a/src/ProtonVPN.App/Profiles/Form/BaseCountryServerProfileFormViewModel.cs b/src/ProtonVPN.App/Profiles/Form/BaseCountryServerProfileFormViewModel.cs index 0fa5133f7..aaaafca27 100644 --- a/src/ProtonVPN.App/Profiles/Form/BaseCountryServerProfileFormViewModel.cs +++ b/src/ProtonVPN.App/Profiles/Form/BaseCountryServerProfileFormViewModel.cs @@ -28,7 +28,7 @@ using ProtonVPN.Core.Servers.Models; using ProtonVPN.Core.Servers.Specs; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; using ProtonVPN.Modals.Upsell; using ProtonVPN.Profiles.Servers; using Profile = ProtonVPN.Core.Profiles.Profile; @@ -177,7 +177,7 @@ private bool IsUpgradeRequiredForCountry(string countryCode) { Specification spec = new ServerByFeatures(GetFeatures()) && new ExitCountryServer(countryCode) && - new MaxTierServer(UserStorage.User().MaxTier); + new MaxTierServer(UserStorage.GetUser().MaxTier); return !ServerManager.GetServers(spec).Any(); } diff --git a/src/ProtonVPN.App/Properties/Settings.Designer.cs b/src/ProtonVPN.App/Properties/Settings.Designer.cs index 4c17b1e1a..0d7f287d4 100644 --- a/src/ProtonVPN.App/Properties/Settings.Designer.cs +++ b/src/ProtonVPN.App/Properties/Settings.Designer.cs @@ -1565,5 +1565,17 @@ public bool IsToShowRebrandingPopup { this["IsToShowRebrandingPopup"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string DnsCache { + get { + return ((string)(this["DnsCache"])); + } + set { + this["DnsCache"] = value; + } + } } } diff --git a/src/ProtonVPN.App/Properties/Settings.settings b/src/ProtonVPN.App/Properties/Settings.settings index 801c72e9f..ceebce9f2 100644 --- a/src/ProtonVPN.App/Properties/Settings.settings +++ b/src/ProtonVPN.App/Properties/Settings.settings @@ -389,5 +389,8 @@ False + + + \ No newline at end of file diff --git a/src/ProtonVPN.App/ProtonVPN.App.csproj b/src/ProtonVPN.App/ProtonVPN.App.csproj index 8dfa5d5e4..eb3c9ce98 100644 --- a/src/ProtonVPN.App/ProtonVPN.App.csproj +++ b/src/ProtonVPN.App/ProtonVPN.App.csproj @@ -89,7 +89,6 @@ - @@ -1318,7 +1317,6 @@ Code - COPYING.md PreserveNewest @@ -1613,6 +1611,14 @@ + + {455DA1FB-5097-47D2-8603-B0E1F9D90294} + ProtonVPN.Dns.Contracts + + + {e52e34fd-40ae-425b-9c1a-2cab3ee2704f} + ProtonVPN.Dns.Installers + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} ProtonVPN.Api.Contracts @@ -1919,9 +1925,6 @@ - - call "$(ProjectDir)gosrp.bat" - owjd ewh e qo"); + IHttpResponseMessage response = HttpResponseFromString("aj shhd ajh khfk owjd ewh e qo"); _httpClient.GetAsync(p2PStatusUri).Returns(response); - var subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(10)); + BlockedTraffic subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(10)); // Act - var result = await subject.Detected(); + bool result = await subject.Detected(); // Assert result.Should().BeTrue(); } @@ -92,11 +92,11 @@ public async Task Detected_ShouldBeFalse_WhenHttpResponse_DoesNotContainPattern( { // Arrange Uri p2PStatusUri = new Uri("http://protonstatus.test.com/vpn_status_ppp"); - var response = HttpResponseFromString("aj shhd ajh khfk owjd ewh e qo"); + IHttpResponseMessage response = HttpResponseFromString("aj shhd ajh khfk owjd ewh e qo"); _httpClient.GetAsync(p2PStatusUri).Returns(response); - var subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(11)); + BlockedTraffic subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(11)); // Act - var result = await subject.Detected(); + bool result = await subject.Detected(); // Assert result.Should().BeFalse(); } @@ -106,11 +106,11 @@ public async Task Detected_ShouldBeFalse_WhenHttpResponseCode_IsNotSuccess() { // Arrange Uri p2PStatusUri = new Uri("http://protonstatus.test.com/vpn_status_ppp"); - var response = FailedHttpResponse(); + Task response = FailedHttpResponse(); _httpClient.GetAsync(p2PStatusUri).Returns(response); - var subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(19)); + BlockedTraffic subject = new BlockedTraffic(_httpClients, p2PStatusUri, TimeSpan.FromSeconds(19)); // Act - var result = await subject.Detected(); + bool result = await subject.Detected(); // Assert result.Should().BeFalse(); } @@ -119,7 +119,7 @@ public async Task Detected_ShouldBeFalse_WhenHttpResponseCode_IsNotSuccess() private static Task FailedHttpResponse() { - var httpResponse = Substitute.For(); + IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(false); return Task.FromResult(httpResponse); @@ -127,7 +127,7 @@ private static Task FailedHttpResponse() private static IHttpResponseMessage HttpResponseFromString(string content) { - var httpResponse = Substitute.For(); + IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(true); httpResponse.Content.ReadAsStringAsync().Returns(content); diff --git a/test/ProtonVPN.App.Test/P2PDetection/Blocked/SafeBlockedTrafficTest.cs b/src/Tests/ProtonVPN.App.Tests/P2PDetection/Blocked/SafeBlockedTrafficTest.cs similarity index 98% rename from test/ProtonVPN.App.Test/P2PDetection/Blocked/SafeBlockedTrafficTest.cs rename to src/Tests/ProtonVPN.App.Tests/P2PDetection/Blocked/SafeBlockedTrafficTest.cs index 6cb30a83a..43d162ff7 100644 --- a/test/ProtonVPN.App.Test/P2PDetection/Blocked/SafeBlockedTrafficTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/P2PDetection/Blocked/SafeBlockedTrafficTest.cs @@ -17,17 +17,17 @@ * along with ProtonVPN. If not, see . */ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using NSubstitute.ExceptionExtensions; using ProtonVPN.P2PDetection.Blocked; -using System; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -using System.Threading.Tasks; -namespace ProtonVPN.App.Test.P2PDetection.Blocked +namespace ProtonVPN.App.Tests.P2PDetection.Blocked { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] diff --git a/test/ProtonVPN.App.Test/P2PDetection/P2PDetectionTimeoutTest.cs b/src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectionTimeoutTest.cs similarity index 96% rename from test/ProtonVPN.App.Test/P2PDetection/P2PDetectionTimeoutTest.cs rename to src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectionTimeoutTest.cs index 7a5479c1f..406d938bc 100644 --- a/test/ProtonVPN.App.Test/P2PDetection/P2PDetectionTimeoutTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectionTimeoutTest.cs @@ -22,7 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.P2PDetection; -namespace ProtonVPN.App.Test.P2PDetection +namespace ProtonVPN.App.Tests.P2PDetection { [TestClass] public class P2PDetectionTimeoutTest diff --git a/test/ProtonVPN.App.Test/P2PDetection/P2PDetectorTest.cs b/src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectorTest.cs similarity index 99% rename from test/ProtonVPN.App.Test/P2PDetection/P2PDetectorTest.cs rename to src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectorTest.cs index 7d72191f6..5aa70518b 100644 --- a/test/ProtonVPN.App.Test/P2PDetection/P2PDetectorTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/P2PDetection/P2PDetectorTest.cs @@ -36,7 +36,7 @@ using PhysicalServer = ProtonVPN.Core.Servers.Models.PhysicalServer; using Server = ProtonVPN.Core.Servers.Models.Server; -namespace ProtonVPN.App.Test.P2PDetection +namespace ProtonVPN.App.Tests.P2PDetection { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] diff --git a/test/ProtonVPN.App.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.App.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.App.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.App.Tests/Properties/AssemblyInfo.cs index 445ef39ff..9b6caa630 100644 --- a/test/ProtonVPN.App.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.App.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.App.Test")] +[assembly: AssemblyTitle("ProtonVPN.App.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.App.Test")] +[assembly: AssemblyProduct("ProtonVPN.App.Tests")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/test/ProtonVPN.App.Test/ProtonVPN.App.Test.csproj b/src/Tests/ProtonVPN.App.Tests/ProtonVPN.App.Tests.csproj similarity index 57% rename from test/ProtonVPN.App.Test/ProtonVPN.App.Test.csproj rename to src/Tests/ProtonVPN.App.Tests/ProtonVPN.App.Tests.csproj index 2447f1e51..2fa63640e 100644 --- a/test/ProtonVPN.App.Test/ProtonVPN.App.Test.csproj +++ b/src/Tests/ProtonVPN.App.Tests/ProtonVPN.App.Tests.csproj @@ -1,14 +1,13 @@  - Debug AnyCPU {7F48FD5C-A4C6-496A-B68E-265237C22330} Library Properties - ProtonVPN.App.Test - ProtonVPN.App.Test + ProtonVPN.App.Tests + ProtonVPN.App.Tests v4.7.2 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -25,7 +24,7 @@ true full false - ..\..\src\bin\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -34,7 +33,7 @@ pdbonly true - ..\..\src\bin\ + ..\..\bin\ TRACE prompt 4 @@ -44,34 +43,7 @@ true - - ..\..\packages\Autofac.4.9.4\lib\net45\Autofac.dll - - - ..\..\packages\Caliburn.Micro.Core.3.2.0\lib\net45\Caliburn.Micro.dll - - - ..\..\packages\Caliburn.Micro.3.2.0\lib\net45\Caliburn.Micro.Platform.dll - - - ..\..\packages\Caliburn.Micro.3.2.0\lib\net45\Caliburn.Micro.Platform.Core.dll - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - @@ -79,16 +51,7 @@ - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\Caliburn.Micro.3.2.0\lib\net45\System.Windows.Interactivity.dll - @@ -138,37 +101,37 @@ - + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} ProtonVPN.Api.Contracts - + {3E905528-D87C-4552-A32D-66BF90D14DB0} ProtonVPN.Api - + {0CDCA012-BB2D-49B3-944E-CE80D75D651A} ProtonVPN.App - + {03B8E43C-5680-4803-A745-0A104FE6620C} ProtonVPN.Common - + {ca44b51d-7645-413a-818f-2c5b57db67dd} ProtonVPN.Core - + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} ProtonVPN.Crypto - + {45A0EA81-D37E-4D7F-8CE1-CE6B6A95A9ED} ProtonVPN.Resource - + {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common + ProtonVPN.Tests.Common @@ -190,16 +153,33 @@ - + + + + 4.9.4 + + + 3.2.0 + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 4.3.0 + + + 5.0.0 + + + 4.5.4 + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.App.Test/Servers/ServerListFactoryTest.cs b/src/Tests/ProtonVPN.App.Tests/Servers/ServerListFactoryTest.cs similarity index 98% rename from test/ProtonVPN.App.Test/Servers/ServerListFactoryTest.cs rename to src/Tests/ProtonVPN.App.Tests/Servers/ServerListFactoryTest.cs index 0f11fac92..1fedecde2 100644 --- a/test/ProtonVPN.App.Test/Servers/ServerListFactoryTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/Servers/ServerListFactoryTest.cs @@ -31,7 +31,7 @@ using ProtonVPN.Servers; using ProtonVPN.Streaming; -namespace ProtonVPN.App.Test.Servers +namespace ProtonVPN.App.Tests.Servers { [TestClass] public class ServerListFactoryTest @@ -89,7 +89,7 @@ private void InitializeUserStorage() { _userStorage = Substitute.For(); _user = new User(); - _userStorage.User().Returns(_user); + _userStorage.GetUser().Returns(_user); } private LogicalServerResponse CreateServer(string name, Features features, string exitCountryCode, @@ -142,7 +142,7 @@ public void TestCleanup() public void BuildServerList_ItShouldDisplayFreeCountriesForFreeUserFirst() { // Arrange - _userStorage.User().Returns(new User {MaxTier = ServerTiers.Free}); + _userStorage.GetUser().Returns(new User {MaxTier = ServerTiers.Free}); // Act ObservableCollection result = _serverListFactory.BuildServerList(); diff --git a/test/ProtonVPN.App.Test/Settings/ReconnectStateTest.cs b/src/Tests/ProtonVPN.App.Tests/Settings/ReconnectStateTest.cs similarity index 93% rename from test/ProtonVPN.App.Test/Settings/ReconnectStateTest.cs rename to src/Tests/ProtonVPN.App.Tests/Settings/ReconnectStateTest.cs index f942a1720..050c803e8 100644 --- a/test/ProtonVPN.App.Test/Settings/ReconnectStateTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/Settings/ReconnectStateTest.cs @@ -26,7 +26,7 @@ using ProtonVPN.Core.Vpn; using ProtonVPN.Settings.ReconnectNotification; -namespace ProtonVPN.App.Test.Settings +namespace ProtonVPN.App.Tests.Settings { [TestClass] public class ReconnectStateTest @@ -46,7 +46,7 @@ public async Task ReconnectShouldBeRequiredOnlyIfChangesPending() { // Arrange _appSettings.OvpnProtocol.Returns("tcp"); - var sut = new ReconnectState(_settingsBuilder); + ReconnectState sut = new ReconnectState(_settingsBuilder); await sut.OnVpnStateChanged(GetVpnStateEventArgs(VpnStatus.Connected)); _appSettings.OvpnProtocol.Returns("udp"); @@ -59,7 +59,7 @@ public async Task ReconnectShouldNotBeRequiredIfDisconnected() { // Arrange _appSettings.OvpnProtocol.Returns("tcp"); - var sut = new ReconnectState(_settingsBuilder); + ReconnectState sut = new ReconnectState(_settingsBuilder); await sut.OnVpnStateChanged(GetVpnStateEventArgs(VpnStatus.Connected)); _appSettings.OvpnProtocol.Returns("udp"); diff --git a/test/ProtonVPN.App.Test/TestData/bug-report-test-2.txt b/src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-2.txt similarity index 100% rename from test/ProtonVPN.App.Test/TestData/bug-report-test-2.txt rename to src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-2.txt diff --git a/test/ProtonVPN.App.Test/TestData/bug-report-test-3.txt b/src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-3.txt similarity index 100% rename from test/ProtonVPN.App.Test/TestData/bug-report-test-3.txt rename to src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-3.txt diff --git a/test/ProtonVPN.App.Test/TestData/bug-report-test-4.txt b/src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-4.txt similarity index 100% rename from test/ProtonVPN.App.Test/TestData/bug-report-test-4.txt rename to src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test-4.txt diff --git a/test/ProtonVPN.App.Test/TestData/bug-report-test.txt b/src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test.txt similarity index 100% rename from test/ProtonVPN.App.Test/TestData/bug-report-test.txt rename to src/Tests/ProtonVPN.App.Tests/TestData/bug-report-test.txt diff --git a/test/ProtonVPN.App.Test/Vpn/Connectors/GuestHoleConnectorTest.cs b/src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/GuestHoleConnectorTest.cs similarity index 98% rename from test/ProtonVPN.App.Test/Vpn/Connectors/GuestHoleConnectorTest.cs rename to src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/GuestHoleConnectorTest.cs index a64a48e74..2eafad470 100644 --- a/test/ProtonVPN.App.Test/Vpn/Connectors/GuestHoleConnectorTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/GuestHoleConnectorTest.cs @@ -32,7 +32,7 @@ using ProtonVPN.Core.Vpn; using ProtonVPN.Vpn.Connectors; -namespace ProtonVPN.App.Test.Vpn.Connectors +namespace ProtonVPN.App.Tests.Vpn.Connectors { [TestClass] public class GuestHoleConnectorTest diff --git a/test/ProtonVPN.App.Test/Vpn/Connectors/ProfileConnectorTest.cs b/src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/ProfileConnectorTest.cs similarity index 98% rename from test/ProtonVPN.App.Test/Vpn/Connectors/ProfileConnectorTest.cs rename to src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/ProfileConnectorTest.cs index 8452efefb..a2af42101 100644 --- a/test/ProtonVPN.App.Test/Vpn/Connectors/ProfileConnectorTest.cs +++ b/src/Tests/ProtonVPN.App.Tests/Vpn/Connectors/ProfileConnectorTest.cs @@ -42,7 +42,7 @@ using ProtonVPN.Windows.Popups.Delinquency; using Profile = ProtonVPN.Core.Profiles.Profile; -namespace ProtonVPN.App.Test.Vpn.Connectors +namespace ProtonVPN.App.Tests.Vpn.Connectors { [TestClass] public class ProfileConnectorTest @@ -101,7 +101,7 @@ private void InitializeUser() { MaxTier = ServerTiers.Plus }; - _userStorage.User().Returns(user); + _userStorage.GetUser().Returns(user); } private Result GetVpnCredentials() diff --git a/test/TestTools.ProfileCleaner/app.config b/src/Tests/ProtonVPN.App.Tests/app.config similarity index 72% rename from test/TestTools.ProfileCleaner/app.config rename to src/Tests/ProtonVPN.App.Tests/app.config index 4e025ec35..ba0ac79da 100644 --- a/test/TestTools.ProfileCleaner/app.config +++ b/src/Tests/ProtonVPN.App.Tests/app.config @@ -6,10 +6,6 @@ - - - - diff --git a/test/ProtonVPN.Common.Test/Cli/CommandLineOptionTest.cs b/src/Tests/ProtonVPN.Common.Tests/Cli/CommandLineOptionTest.cs similarity index 62% rename from test/ProtonVPN.Common.Test/Cli/CommandLineOptionTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Cli/CommandLineOptionTest.cs index 5ce5baa8a..b1f72af9a 100644 --- a/test/ProtonVPN.Common.Test/Cli/CommandLineOptionTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Cli/CommandLineOptionTest.cs @@ -18,13 +18,14 @@ */ using System; +using System.Collections.Generic; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Cli; // ReSharper disable ObjectCreationAsStatement -namespace ProtonVPN.Common.Test.Cli +namespace ProtonVPN.Common.Tests.Cli { [TestClass] public class CommandLineOptionTest @@ -40,9 +41,9 @@ public void Option_ShouldThrow_WhenArgsIsNull() [TestMethod] public void Exists_ShouldBeFalse_WhenEmptyArgs() { - var option = new CommandLineOption("zero", new string[0]); + CommandLineOption option = new("zero", new string[0]); - var result = option.Exists(); + bool result = option.Exists(); result.Should().BeFalse(); } @@ -50,10 +51,10 @@ public void Exists_ShouldBeFalse_WhenEmptyArgs() [TestMethod] public void Exists_ShouldBeFalse_WhenNoOptionInArgs() { - var args = new [] { "zero", "one", "-two", "--three", "--h", "z", "-z", "--z", "0", "\"day.com\"" }; - var option = new CommandLineOption("zero", args); + string[] args = new [] { "zero", "one", "-two", "--three", "--h", "z", "-z", "--z", "0", "\"day.com\"" }; + CommandLineOption option = new("zero", args); - var result = option.Exists(); + bool result = option.Exists(); result.Should().BeFalse(); } @@ -61,10 +62,10 @@ public void Exists_ShouldBeFalse_WhenNoOptionInArgs() [TestMethod] public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithForwardSlash() { - var args = new[] { "/zero", "one", "-two", "--three" }; - var option = new CommandLineOption("zero", args); + string[] args = new[] { "/zero", "one", "-two", "--three" }; + CommandLineOption option = new("zero", args); - var result = option.Exists(); + bool result = option.Exists(); result.Should().BeTrue(); } @@ -72,10 +73,10 @@ public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithForwardSlash() [TestMethod] public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithMinus() { - var args = new[] { "one", "-zero", "-two", "--three" }; - var option = new CommandLineOption("zero", args); + string[] args = new[] { "one", "-zero", "-two", "--three" }; + CommandLineOption option = new("zero", args); - var result = option.Exists(); + bool result = option.Exists(); result.Should().BeTrue(); } @@ -83,10 +84,10 @@ public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithMinus() [TestMethod] public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithDoubleMinus() { - var args = new[] { "one", "-two", "--three", "--zero" }; - var option = new CommandLineOption("zero", args); + string[] args = new[] { "one", "-two", "--three", "--zero" }; + CommandLineOption option = new("zero", args); - var result = option.Exists(); + bool result = option.Exists(); result.Should().BeTrue(); } @@ -94,10 +95,10 @@ public void Exists_ShouldBeTrue_WhenOptionInArgs_PrecededWithDoubleMinus() [TestMethod] public void Params_ShouldBe_Empty() { - var args = new[] { "one", "-two", "--zero" }; - var option = new CommandLineOption("zero", args); + string[] args = new[] { "one", "-two", "--zero" }; + CommandLineOption option = new("zero", args); - var result = option.Params(); + IReadOnlyList result = option.Params(); result.Should().BeEmpty(); } @@ -105,11 +106,11 @@ public void Params_ShouldBe_Empty() [TestMethod] public void Params_ShouldBe_ArgsAfterOption_TillEnd() { - var args = new[] { "--zero", "three", "\"http://daily.com\"" }; - var expected = new[] { "three", "\"http://daily.com\"" }; - var option = new CommandLineOption("zero", args); + string[] args = new[] { "--zero", "three", "\"http://daily.com\"" }; + string[] expected = new[] { "three", "\"http://daily.com\"" }; + CommandLineOption option = new("zero", args); - var result = option.Params(); + IReadOnlyList result = option.Params(); result.Should().ContainInOrder(expected); } diff --git a/test/ProtonVPN.Common.Test/Configuration/Source/CustomConfigTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Source/CustomConfigTest.cs similarity index 76% rename from test/ProtonVPN.Common.Test/Configuration/Source/CustomConfigTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Source/CustomConfigTest.cs index a90d33b94..6f86e8ed8 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Source/CustomConfigTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Source/CustomConfigTest.cs @@ -21,10 +21,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using NSubstitute.ReturnsExtensions; +using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Configuration.Source; using ProtonVPN.Common.Configuration.Storage; -namespace ProtonVPN.Common.Test.Configuration.Source +namespace ProtonVPN.Common.Tests.Configuration.Source { [TestClass] public class CustomConfigTest @@ -43,11 +44,11 @@ public void TestInitialize() public void Value_ShouldBe_Default_WhenMode_IsDefault() { // Arrange - var defaultValue = new DefaultConfig().Value(); + Config defaultValue = new DefaultConfig().Value(); _default.Value().Returns(defaultValue); - var config = new CustomConfig(ConfigMode.Default, _default, _storage); + CustomConfig config = new(ConfigMode.Default, _default, _storage); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().Be(defaultValue); } @@ -56,12 +57,12 @@ public void Value_ShouldBe_Default_WhenMode_IsDefault() public void Value_ShouldBe_StorageValue_WhenMode_IsNotDefault() { // Arrange - var value = new DefaultConfig().Value(); + Config value = new DefaultConfig().Value(); _default.Value().Returns(new DefaultConfig().Value()); _storage.Value().Returns(value); - var config = new CustomConfig(ConfigMode.CustomOrDefault, _default, _storage); + CustomConfig config = new(ConfigMode.CustomOrDefault, _default, _storage); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().Be(value); } @@ -70,12 +71,12 @@ public void Value_ShouldBe_StorageValue_WhenMode_IsNotDefault() public void Value_ShouldBe_Default_WhenMode_IsNotDefault_AndStorageValue_IsNull() { // Arrange - var defaultValue = new DefaultConfig().Value(); + Config defaultValue = new DefaultConfig().Value(); _default.Value().Returns(defaultValue); _storage.Value().ReturnsNull(); - var config = new CustomConfig(ConfigMode.CustomOrDefault, _default, _storage); + CustomConfig config = new(ConfigMode.CustomOrDefault, _default, _storage); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().Be(defaultValue); } @@ -84,10 +85,10 @@ public void Value_ShouldBe_Default_WhenMode_IsNotDefault_AndStorageValue_IsNull( public void Value_ShouldCall_StorageSave_WhenMode_IsCustomOrSavedDefault_AndStorageValue_IsNull() { // Arrange - var defaultValue = new DefaultConfig().Value(); + Config defaultValue = new DefaultConfig().Value(); _default.Value().Returns(defaultValue); _storage.Value().ReturnsNull(); - var config = new CustomConfig(ConfigMode.CustomOrSavedDefault, _default, _storage); + CustomConfig config = new(ConfigMode.CustomOrSavedDefault, _default, _storage); // Act config.Value(); // Assert diff --git a/test/ProtonVPN.Common.Test/Configuration/Source/DefaultAppConfigTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Source/DefaultAppConfigTest.cs similarity index 87% rename from test/ProtonVPN.Common.Test/Configuration/Source/DefaultAppConfigTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Source/DefaultAppConfigTest.cs index 723ca102f..f66291c1e 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Source/DefaultAppConfigTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Source/DefaultAppConfigTest.cs @@ -20,10 +20,11 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; +using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Configuration.Source; using ProtonVPN.Common.Configuration.Storage; -namespace ProtonVPN.Common.Test.Configuration.Source +namespace ProtonVPN.Common.Tests.Configuration.Source { [TestClass] public class DefaultAppConfigTest @@ -40,10 +41,10 @@ public void TestInitialize() public void Value_ShouldPass_Validation() { // Arrange - var config = new DefaultConfig().Value(); + Config config = new DefaultConfig().Value(); _storage.Value().Returns(config); // Act - var valid = new ValidatedConfigStorage(_storage); + ValidatedConfigStorage valid = new(_storage); // Assert valid.Should().NotBeNull(); } diff --git a/test/ProtonVPN.Common.Test/Configuration/Storage/ConfigFileTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ConfigFileTest.cs similarity index 89% rename from test/ProtonVPN.Common.Test/Configuration/Storage/ConfigFileTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ConfigFileTest.cs index 50c5bd3c4..c2a69a9f6 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Storage/ConfigFileTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ConfigFileTest.cs @@ -22,7 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Configuration.Storage; -namespace ProtonVPN.Common.Test.Configuration.Storage +namespace ProtonVPN.Common.Tests.Configuration.Storage { [TestClass] public class ConfigFileTest @@ -31,9 +31,9 @@ public class ConfigFileTest public void Path_ShouldBe_Default() { const string expected = "ProtonVPN.config"; - var file = new ConfigFile(); + ConfigFile file = new(); - var result = file.Path(); + string result = file.Path(); Path.GetFileName(result).Should().Be(expected); } diff --git a/test/ProtonVPN.Common.Test/Configuration/Storage/FileConfigStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/FileConfigStorageTest.cs similarity index 82% rename from test/ProtonVPN.Common.Test/Configuration/Storage/FileConfigStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/FileConfigStorageTest.cs index f22aab043..425322b3a 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Storage/FileConfigStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/FileConfigStorageTest.cs @@ -22,12 +22,13 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; +using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Configuration.Source; using ProtonVPN.Common.Configuration.Storage; using ProtonVPN.Common.Extensions; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; -namespace ProtonVPN.Common.Test.Configuration.Storage +namespace ProtonVPN.Common.Tests.Configuration.Storage { [TestClass] public class FileConfigStorageTest @@ -36,9 +37,9 @@ public class FileConfigStorageTest public void Value_ShouldBe_NotNull_WhenFileExists() { // Arrange - var storage = new FileConfigStorage(ConfigFile("Config.json")); + FileConfigStorage storage = new(ConfigFile("Config.json")); // Act - var result = storage.Value(); + Config result = storage.Value(); // Assert result.Should().NotBeNull(); } @@ -47,7 +48,7 @@ public void Value_ShouldBe_NotNull_WhenFileExists() public void Value_ShouldThrow_FileAccessException_WhenFileDoesNotExist() { // Arrange - var storage = new FileConfigStorage(ConfigFile("Does-not-exist.json")); + FileConfigStorage storage = new(ConfigFile("Does-not-exist.json")); // Act Action action = () => storage.Value(); // Assert @@ -59,7 +60,7 @@ public void Value_ShouldThrow_FileAccessException_WhenFileDoesNotExist() public void Value_ShouldThrow_FileAccessException_WhenFolderDoesNotExist() { // Arrange - var storage = new FileConfigStorage(ConfigFile("Does-not-exist\\Config.json")); + FileConfigStorage storage = new(ConfigFile("Does-not-exist\\Config.json")); // Act Action action = () => storage.Value(); // Assert @@ -72,8 +73,8 @@ public void Save_ShouldSave_ToFile() { // Arrange const string filename = "Saved-app-config.json"; - var storage = new FileConfigStorage(ConfigFile(filename)); - var config = new DefaultConfig().Value(); + FileConfigStorage storage = new(ConfigFile(filename)); + Config config = new DefaultConfig().Value(); // Act storage.Save(config); // Assert diff --git a/test/ProtonVPN.Common.Test/Configuration/Storage/SafeConfigStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/SafeConfigStorageTest.cs similarity index 81% rename from test/ProtonVPN.Common.Test/Configuration/Storage/SafeConfigStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/SafeConfigStorageTest.cs index 8be9e336a..48060ce6d 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Storage/SafeConfigStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/SafeConfigStorageTest.cs @@ -26,7 +26,7 @@ using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Configuration.Storage; -namespace ProtonVPN.Common.Test.Configuration.Storage +namespace ProtonVPN.Common.Tests.Configuration.Storage { [TestClass] public class SafeConfigStorageTest @@ -43,11 +43,11 @@ public void TestInitialize() public void Value_ShouldBe_OriginValue() { // Arrange - var expected = new Config(); + Config expected = new(); _origin.Value().Returns(expected); - var storage = new SafeConfigStorage(_origin); + SafeConfigStorage storage = new(_origin); // Act - var value = storage.Value(); + Config value = storage.Value(); // Assert value.Should().Be(expected); } @@ -59,11 +59,11 @@ public void Value_ShouldBe_OriginValue() public void Value_ShouldBeNull_WhenOriginThrows_ExpectedException(Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.When(x => x.Value()).Do(_ => throw exception); - var storage = new SafeConfigStorage(_origin); + SafeConfigStorage storage = new(_origin); // Act - var result = storage.Value(); + Config result = storage.Value(); // Assert result.Should().BeNull(); } @@ -72,8 +72,8 @@ public void Value_ShouldBeNull_WhenOriginThrows_ExpectedException(Type exception public void Value_ShouldPass_UnexpectedException() { // Arrange - _origin.When(x => x.Value()).Do(_ => throw new Exception()); - var storage = new SafeConfigStorage(_origin); + _origin.When(x => x.Value()).Do(_ => throw new()); + SafeConfigStorage storage = new(_origin); // Act Action action = () => storage.Value(); // Assert diff --git a/test/ProtonVPN.Common.Test/Configuration/Storage/ValidatedConfigStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ValidatedConfigStorageTest.cs similarity index 79% rename from test/ProtonVPN.Common.Test/Configuration/Storage/ValidatedConfigStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ValidatedConfigStorageTest.cs index d474a5b93..ee2f00203 100644 --- a/test/ProtonVPN.Common.Test/Configuration/Storage/ValidatedConfigStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Configuration/Storage/ValidatedConfigStorageTest.cs @@ -21,10 +21,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using NSubstitute.ReturnsExtensions; +using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Configuration.Source; using ProtonVPN.Common.Configuration.Storage; -namespace ProtonVPN.Common.Test.Configuration.Storage +namespace ProtonVPN.Common.Tests.Configuration.Storage { [TestClass] public class ValidatedConfigStorageTest @@ -41,11 +42,11 @@ public void TestInitialize() public void Value_ShouldBe_OriginValue_WhenValid() { // Arrange - var contract = new DefaultConfig().Value(); + Config contract = new DefaultConfig().Value(); _origin.Value().Returns(contract); - var config = new ValidatedConfigStorage(_origin); + ValidatedConfigStorage config = new(_origin); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().Be(contract); } @@ -54,12 +55,12 @@ public void Value_ShouldBe_OriginValue_WhenValid() public void Value_ShouldBeNull_WhenNotValid() { // Arrange - var contract = new DefaultConfig().Value(); + Config contract = new DefaultConfig().Value(); contract.ReportBugMaxFiles = -1; _origin.Value().Returns(contract); - var config = new ValidatedConfigStorage(_origin); + ValidatedConfigStorage config = new(_origin); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().BeNull(); } @@ -69,9 +70,9 @@ public void Value_ShouldBeNull_WhenNull() { // Arrange _origin.Value().ReturnsNull(); - var config = new ValidatedConfigStorage(_origin); + ValidatedConfigStorage config = new(_origin); // Act - var result = config.Value(); + Config result = config.Value(); // Assert result.Should().BeNull(); } diff --git a/src/Tests/ProtonVPN.Common.Tests/Extensions/ByteExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Extensions/ByteExtensionsTest.cs new file mode 100644 index 000000000..cfa4ad9fd --- /dev/null +++ b/src/Tests/ProtonVPN.Common.Tests/Extensions/ByteExtensionsTest.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Common.Extensions; + +namespace ProtonVPN.Common.Tests.Extensions +{ + [TestClass] + public class ByteExtensionsTest + { + [DataTestMethod] + [DataRow(null, null)] + [DataRow(new byte[]{}, new byte[]{})] + [DataRow(new byte[]{0}, new byte[]{})] + [DataRow(new byte[]{1}, new byte[]{1})] + [DataRow(new byte[]{0,0}, new byte[]{})] + [DataRow(new byte[]{2,0}, new byte[]{2})] + [DataRow(new byte[]{0,3}, new byte[]{0,3})] + [DataRow(new byte[]{4,5}, new byte[]{4,5})] + [DataRow(new byte[]{0,0,0}, new byte[]{})] + [DataRow(new byte[]{6,0,0}, new byte[]{6})] + [DataRow(new byte[]{0,7,0}, new byte[]{0,7})] + [DataRow(new byte[]{8,9,1}, new byte[]{8,9,1})] + [DataRow(new byte[]{0,0,0,0,0,0,0,0,0}, new byte[]{})] + [DataRow(new byte[]{2,0,0,0,0,0,0,0,0}, new byte[]{2})] + [DataRow(new byte[]{0,0,0,0,3,0,0,0,0}, new byte[]{0,0,0,0,3})] + [DataRow(new byte[]{4,5,6,7,8,0,0,0,0}, new byte[]{4,5,6,7,8})] + [DataRow(new byte[]{0,0,0,0,0,0,0,9,0}, new byte[]{0,0,0,0,0,0,0,9})] + [DataRow(new byte[]{1,2,3,4,5,6,7,8,0}, new byte[]{1,2,3,4,5,6,7,8})] + [DataRow(new byte[]{0,0,0,0,0,0,0,0,9}, new byte[]{0,0,0,0,0,0,0,0,9})] + [DataRow(new byte[]{1,2,3,4,5,6,7,8,9}, new byte[]{1,2,3,4,5,6,7,8,9})] + public void TestTrimTrailingZeroBytes(byte[] argument, byte[] expectedResult) + { + byte[] result = argument.TrimTrailingZeroBytes(); + result.Should().BeEquivalentTo(expectedResult); + } + } +} \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/Extensions/ExceptionTypeExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Extensions/ExceptionTypeExtensionsTest.cs similarity index 75% rename from test/ProtonVPN.Common.Test/Extensions/ExceptionTypeExtensionsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Extensions/ExceptionTypeExtensionsTest.cs index 2ba9709f0..a725d54c8 100644 --- a/test/ProtonVPN.Common.Test/Extensions/ExceptionTypeExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Extensions/ExceptionTypeExtensionsTest.cs @@ -25,7 +25,7 @@ using NSubstitute; using ProtonVPN.Common.Extensions; -namespace ProtonVPN.Common.Test.Extensions +namespace ProtonVPN.Common.Tests.Extensions { [TestClass] public class ExceptionTypeExtensionsTest @@ -36,12 +36,12 @@ public class ExceptionTypeExtensionsTest public void IsExpectedExceptionOf_ShouldBe_Origin_IsExpectedException(bool expected) { // Arrange - var exception = new Exception(); - var origin = Substitute.For(); + Exception exception = new(); + IThrowsExpectedExceptions origin = Substitute.For(); origin.IsExpectedException(exception).Returns(expected); // Act - var result = exception.IsExpectedExceptionOf(origin); + bool result = exception.IsExpectedExceptionOf(origin); // Assert result.Should().Be(expected); @@ -55,10 +55,10 @@ public void IsExpectedExceptionOf_ShouldBe_Origin_IsExpectedException(bool expec public void IsFileAccessException_ShouldBe(bool expected, Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); // Act - var result = exception.IsFileAccessException(); + bool result = exception.IsFileAccessException(); // Assert result.Should().Be(expected); @@ -71,10 +71,10 @@ public void IsFileAccessException_ShouldBe(bool expected, Type exceptionType) public void IsCommunicationException_ShouldBe(bool expected, Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); // Act - var result = exception.IsServiceCommunicationException(); + bool result = exception.IsServiceCommunicationException(); // Assert result.Should().Be(expected); @@ -84,10 +84,10 @@ public void IsCommunicationException_ShouldBe(bool expected, Type exceptionType) public void IsCommunicationException_ShouldBeFalse_When_ObjectDisposedException() { // Arrange - var exception = new ObjectDisposedException("System.String"); + ObjectDisposedException exception = new("System.String"); // Act - var result = exception.IsServiceCommunicationException(); + bool result = exception.IsServiceCommunicationException(); // Assert result.Should().BeFalse(); @@ -97,10 +97,10 @@ public void IsCommunicationException_ShouldBeFalse_When_ObjectDisposedException( public void IsCommunicationException_ShouldBeTrue_WhenSpecific_ObjectDisposedException() { // Arrange - var exception = new ObjectDisposedException("System.ServiceModel.Channels.ServiceChannel"); + ObjectDisposedException exception = new("System.ServiceModel.Channels.ServiceChannel"); // Act - var result = exception.IsServiceCommunicationException(); + bool result = exception.IsServiceCommunicationException(); // Assert result.Should().BeTrue(); diff --git a/test/ProtonVPN.Common.Test/Extensions/TaskExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Extensions/TaskExtensionsTest.cs similarity index 92% rename from test/ProtonVPN.Common.Test/Extensions/TaskExtensionsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Extensions/TaskExtensionsTest.cs index 4ccf4ff9d..fcb14b33d 100644 --- a/test/ProtonVPN.Common.Test/Extensions/TaskExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Extensions/TaskExtensionsTest.cs @@ -23,10 +23,10 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Extensions; -using ProtonVPN.Test.Common.Breakpoints; +using ProtonVPN.Tests.Common.Breakpoints; using TaskExtensions = ProtonVPN.Common.Extensions.TaskExtensions; -namespace ProtonVPN.Common.Test.Extensions +namespace ProtonVPN.Common.Tests.Extensions { [TestClass] public class TaskExtensionsTest @@ -54,8 +54,8 @@ Task Action(CancellationToken ct) public async Task TimeoutAfter_ShouldThrow_TaskCanceledException_WhenActionCancelled() { // Arrange - Breakpoint breakpoint = new Breakpoint(); - CancellationTokenSource cancellationSource = new CancellationTokenSource(); + Breakpoint breakpoint = new(); + CancellationTokenSource cancellationSource = new(); async Task Action(CancellationToken ct) { await breakpoint.Hit().WaitForContinue(); @@ -78,7 +78,7 @@ async Task Action(CancellationToken ct) public async Task TimeoutAfter_ShouldThrow_TimeoutException_WhenActionTimedOut() { // Arrange - Breakpoint breakpoint = new Breakpoint(); + Breakpoint breakpoint = new(); async Task Action(CancellationToken ct) { await breakpoint.Hit().WaitForContinue(); @@ -104,7 +104,7 @@ public async Task TimeoutAfter_ShouldThrow_WhenActionThrows() // Arrange Task Action(CancellationToken ct) { - throw new Exception(); + throw new(); } // Act diff --git a/test/ProtonVPN.Common.Test/Extensions/TimeSpanExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Extensions/TimeSpanExtensionsTest.cs similarity index 98% rename from test/ProtonVPN.Common.Test/Extensions/TimeSpanExtensionsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Extensions/TimeSpanExtensionsTest.cs index bc55cac2e..1e9a427cd 100644 --- a/test/ProtonVPN.Common.Test/Extensions/TimeSpanExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Extensions/TimeSpanExtensionsTest.cs @@ -22,7 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Extensions; -namespace ProtonVPN.Common.Test.Extensions +namespace ProtonVPN.Common.Tests.Extensions { [TestClass] public class TimeSpanExtensionsTest diff --git a/test/ProtonVPN.Common.Test/Helpers/ConcatenatedSequenceTest.cs b/src/Tests/ProtonVPN.Common.Tests/Helpers/ConcatenatedSequenceTest.cs similarity index 67% rename from test/ProtonVPN.Common.Test/Helpers/ConcatenatedSequenceTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Helpers/ConcatenatedSequenceTest.cs index cf6a04a63..dcb0e023c 100644 --- a/test/ProtonVPN.Common.Test/Helpers/ConcatenatedSequenceTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Helpers/ConcatenatedSequenceTest.cs @@ -17,16 +17,16 @@ * along with ProtonVPN. If not, see . */ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.Common.Helpers; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Common.Helpers; -namespace ProtonVPN.Common.Test.Helpers +namespace ProtonVPN.Common.Tests.Helpers { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -54,12 +54,12 @@ public void ConcatenatedSequence_ShouldThrow_WhenSources_IsEmpty() public void Enumerable_ShouldBeEmpty_WhenAllSourcesAreEmpty() { // Arrange - var source1 = Enumerable.Empty(); - var source2 = Enumerable.Empty(); - var source3 = Enumerable.Empty(); - var subject = new ConcatenatedSequence(source1, source2, source3); + IEnumerable source1 = Enumerable.Empty(); + IEnumerable source2 = Enumerable.Empty(); + IEnumerable source3 = Enumerable.Empty(); + ConcatenatedSequence subject = new ConcatenatedSequence(source1, source2, source3); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().BeEmpty(); } @@ -68,14 +68,14 @@ public void Enumerable_ShouldBeEmpty_WhenAllSourcesAreEmpty() public void Enumerable_ShouldBe_ConcatenatedSources() { // Arrange - var source1 = new [] { "A" }; - var source2 = new [] { "B", "C", "D" }; - var source3 = new [] { "E", "F" }; - var subject = new ConcatenatedSequence(source1, source2, source3); + string[] source1 = new [] { "A" }; + string[] source2 = new [] { "B", "C", "D" }; + string[] source3 = new [] { "E", "F" }; + ConcatenatedSequence subject = new ConcatenatedSequence(source1, source2, source3); - var expected = new[] {"A", "B", "C", "D", "E", "F"}; + string[] expected = new[] {"A", "B", "C", "D", "E", "F"}; // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().BeEquivalentTo(expected); } @@ -84,14 +84,14 @@ public void Enumerable_ShouldBe_ConcatenatedSources() public void NotGeneric_Enumerable_ShouldBe_ConcatenatedSources() { // Arrange - var source1 = new[] { "A", "B", "C" }; - var source2 = new[] { "D", "E" }; - var source3 = new[] { "F" }; - var subject = new ConcatenatedSequence(source1, source2, source3); + string[] source1 = new[] { "A", "B", "C" }; + string[] source2 = new[] { "D", "E" }; + string[] source3 = new[] { "F" }; + ConcatenatedSequence subject = new ConcatenatedSequence(source1, source2, source3); - var expected = new object[] { "A", "B", "C", "D", "E", "F" }; + object[] expected = new object[] { "A", "B", "C", "D", "E", "F" }; // Act - var result = ToList(subject); + List result = ToList(subject); // Assert result.Should().BeEquivalentTo(expected); } @@ -100,10 +100,10 @@ public void NotGeneric_Enumerable_ShouldBe_ConcatenatedSources() private List ToList(IEnumerable source) { - var list = new List(); + List list = new List(); // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var item in source) + foreach (object item in source) list.Add(item); return list; diff --git a/test/ProtonVPN.Common.Test/Helpers/RandomStringsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Helpers/RandomStringsTest.cs similarity index 88% rename from test/ProtonVPN.Common.Test/Helpers/RandomStringsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Helpers/RandomStringsTest.cs index 8e03110ce..a9742be77 100644 --- a/test/ProtonVPN.Common.Test/Helpers/RandomStringsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Helpers/RandomStringsTest.cs @@ -17,12 +17,12 @@ * along with ProtonVPN. If not, see . */ +using System; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Helpers; -using System; -namespace ProtonVPN.Common.Test.Helpers +namespace ProtonVPN.Common.Tests.Helpers { [TestClass] public class ManagementPasswordsTest @@ -34,9 +34,9 @@ public class ManagementPasswordsTest public void Password_ShouldHave_CorrectLength(int length) { // Arrange - var passwords = new RandomStrings(); + RandomStrings passwords = new(); // Act - var result = passwords.RandomString(length); + string result = passwords.RandomString(length); // Assert result.Length.Should().Be(length); } @@ -45,7 +45,7 @@ public void Password_ShouldHave_CorrectLength(int length) public void Password_ShouldThrow_WhenLength_IsNegative() { // Arrange - var passwords = new RandomStrings(); + RandomStrings passwords = new(); // Act Action action = () => passwords.RandomString(-1); // Assert diff --git a/test/ProtonVPN.Common.Test/Logging/LogCleanerTest.cs b/src/Tests/ProtonVPN.Common.Tests/Logging/LogCleanerTest.cs similarity index 93% rename from test/ProtonVPN.Common.Test/Logging/LogCleanerTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Logging/LogCleanerTest.cs index 8cc4dec9e..fc24cf508 100644 --- a/test/ProtonVPN.Common.Test/Logging/LogCleanerTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Logging/LogCleanerTest.cs @@ -26,9 +26,9 @@ using NSubstitute; using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Logging; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; -namespace ProtonVPN.Common.Test.Logging +namespace ProtonVPN.Common.Tests.Logging { [TestClass] public class LogCleanerTest @@ -103,9 +103,9 @@ public void Clean_ShouldSkipLockedFile() CreateEmptyDirectory(logPath); IList files = new[] { "file2.log", "file1.log", "file.log" }.Select(f => Path.Combine(logPath, f)).ToList(); files.ForEach(CreateEmptyFile); - File.SetCreationTimeUtc(files[0], new DateTime(2000, 01, 01, 0, 10, 0)); - File.SetCreationTimeUtc(files[1], new DateTime(2000, 01, 01, 0, 20, 0)); - File.SetCreationTimeUtc(files[2], new DateTime(2000, 01, 01, 0, 30, 0)); + File.SetCreationTimeUtc(files[0], new(2000, 01, 01, 0, 10, 0)); + File.SetCreationTimeUtc(files[1], new(2000, 01, 01, 0, 20, 0)); + File.SetCreationTimeUtc(files[2], new(2000, 01, 01, 0, 30, 0)); LogCleaner cleaner = new(_logger); // Act diff --git a/test/ProtonVPN.Common.Test/OS/Net/NetworkAddressTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkAddressTest.cs similarity index 97% rename from test/ProtonVPN.Common.Test/OS/Net/NetworkAddressTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkAddressTest.cs index 26c8ed61f..f18e92118 100644 --- a/test/ProtonVPN.Common.Test/OS/Net/NetworkAddressTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkAddressTest.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.OS.Net; -namespace ProtonVPN.Common.Test.OS.Net +namespace ProtonVPN.Common.Tests.OS.Net { [TestClass] public class NetworkAddressTest diff --git a/test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs similarity index 84% rename from test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs index 2e66a5028..df7ad8f17 100644 --- a/test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/NullNetworkInterfaceTest.cs @@ -21,7 +21,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.OS.Net.NetworkInterface; -namespace ProtonVPN.Common.Test.OS.Net.NetworkInterface +namespace ProtonVPN.Common.Tests.OS.Net.NetworkInterface { [TestClass] public class NullNetworkInterfaceTest @@ -30,9 +30,9 @@ public class NullNetworkInterfaceTest public void Id_ShouldBe_Empty() { // Arrange - var subject = new NullNetworkInterface(); + NullNetworkInterface subject = new(); // Act - var result = subject.Id; + string result = subject.Id; // Assert result.Should().BeEmpty(); } @@ -41,9 +41,9 @@ public void Id_ShouldBe_Empty() public void IsLoopback_ShouldBe_False() { // Arrange - var subject = new NullNetworkInterface(); + NullNetworkInterface subject = new(); // Act - var result = subject.IsLoopback; + bool result = subject.IsLoopback; // Assert result.Should().BeFalse(); } diff --git a/test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs similarity index 80% rename from test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs index f6d9e368e..f810b59aa 100644 --- a/test/ProtonVPN.Common.Test/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Net/NetworkInterface/SafeSystemNetworkInterfacesTest.cs @@ -29,7 +29,7 @@ using ProtonVPN.Common.Logging.Categorization.Events.NetworkLogs; using ProtonVPN.Common.OS.Net.NetworkInterface; -namespace ProtonVPN.Common.Test.OS.Net.NetworkInterface +namespace ProtonVPN.Common.Tests.OS.Net.NetworkInterface { [TestClass] [SuppressMessage("ReSharper", "AssignmentIsFullyDiscarded")] @@ -49,8 +49,8 @@ public void TestInitialize() public void NetworkAddressChanged_ShouldRaise_WhenOrigin_NetworkAddressChanged_IsRaised() { // Arrange - var wasRaised = false; - var subject = new SafeSystemNetworkInterfaces(_logger, _origin); + bool wasRaised = false; + SafeSystemNetworkInterfaces subject = new(_logger, _origin); subject.NetworkAddressChanged += (s, e) => wasRaised = true; // Act _origin.NetworkAddressChanged += Raise.Event(); @@ -62,11 +62,11 @@ public void NetworkAddressChanged_ShouldRaise_WhenOrigin_NetworkAddressChanged_I public void Interfaces_ShouldBe_Origin_Interfaces() { // Arrange - var expected = new INetworkInterface[] {new TestNetworkInterface("t1"), new TestNetworkInterface("t2")}; + INetworkInterface[] expected = new INetworkInterface[] {new TestNetworkInterface("t1"), new TestNetworkInterface("t2")}; _origin.GetInterfaces().Returns(expected); - var subject = new SafeSystemNetworkInterfaces(_logger, _origin); + SafeSystemNetworkInterfaces subject = new(_logger, _origin); // Act - var result = subject.GetInterfaces(); + INetworkInterface[] result = subject.GetInterfaces(); // Assert _origin.Received().GetInterfaces(); result.Should().HaveCount(2); @@ -77,11 +77,11 @@ public void Interfaces_ShouldBe_Origin_Interfaces() public void Interfaces_ShouldSuppress_ExpectedException(Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.GetInterfaces().Throws(exception); - var subject = new SafeSystemNetworkInterfaces(_logger, _origin); + SafeSystemNetworkInterfaces subject = new(_logger, _origin); // Act - var result = subject.GetInterfaces(); + INetworkInterface[] result = subject.GetInterfaces(); // Assert result.Should().BeEmpty(); } @@ -90,9 +90,9 @@ public void Interfaces_ShouldSuppress_ExpectedException(Type exceptionType) public void Interfaces_ShouldPass_NotExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.GetInterfaces().Throws(exception); - var subject = new SafeSystemNetworkInterfaces(_logger, _origin); + SafeSystemNetworkInterfaces subject = new(_logger, _origin); // Act Action action = () => subject.GetInterfaces(); // Assert @@ -103,9 +103,9 @@ public void Interfaces_ShouldPass_NotExpectedException() public void Interfaces_ShouldLog_ExpectedException() { // Arrange - var exception = new NetworkInformationException(); + NetworkInformationException exception = new(); _origin.GetInterfaces().Throws(exception); - var subject = new SafeSystemNetworkInterfaces(_logger, _origin); + SafeSystemNetworkInterfaces subject = new(_logger, _origin); // Act _ = subject.GetInterfaces(); // Assert diff --git a/test/ProtonVPN.Common.Test/OS/Registry/SafeStartupRecordTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Registry/SafeStartupRecordTest.cs similarity index 76% rename from test/ProtonVPN.Common.Test/OS/Registry/SafeStartupRecordTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Registry/SafeStartupRecordTest.cs index 0fb20a886..9390dd486 100644 --- a/test/ProtonVPN.Common.Test/OS/Registry/SafeStartupRecordTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Registry/SafeStartupRecordTest.cs @@ -27,7 +27,7 @@ using ProtonVPN.Common.Logging; using ProtonVPN.Common.OS.Registry; -namespace ProtonVPN.Common.Test.OS.Registry +namespace ProtonVPN.Common.Tests.OS.Registry { [TestClass] public class SafeStartupRecordTest @@ -48,9 +48,9 @@ public void TestInitialize() public void Exists_ShouldReturn_OriginExists(bool value) { _origin.Exists().Returns(value); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); - var result = record.Exists(); + bool result = record.Exists(); _origin.Received().Exists(); result.Should().Be(value); @@ -62,11 +62,11 @@ public void Exists_ShouldReturn_OriginExists(bool value) [DataRow(typeof(IOException))] public void Exists_ShouldReturnFalse_WhenOriginThrows_RegistryAccessException(Type exceptionType) { - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.Exists().Throws(exception); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); - var result = record.Exists(); + bool result = record.Exists(); _origin.Received().Exists(); result.Should().Be(false); @@ -76,7 +76,7 @@ public void Exists_ShouldReturnFalse_WhenOriginThrows_RegistryAccessException(Ty public void Exists_ShouldThrow_WhenOriginThrows() { _origin.Exists().Throws(new Exception()); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Exists(); @@ -89,9 +89,9 @@ public void Exists_ShouldThrow_WhenOriginThrows() public void Valid_ShouldReturn_OriginValid(bool value) { _origin.Valid().Returns(value); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); - var result = record.Valid(); + bool result = record.Valid(); _origin.Received().Valid(); result.Should().Be(value); @@ -103,11 +103,11 @@ public void Valid_ShouldReturn_OriginValid(bool value) [DataRow(typeof(IOException))] public void Valid_ShouldReturnFalse_WhenOriginThrows_RegistryAccessException(Type exceptionType) { - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.Valid().Throws(exception); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); - var result = record.Valid(); + bool result = record.Valid(); result.Should().Be(false); } @@ -116,7 +116,7 @@ public void Valid_ShouldReturnFalse_WhenOriginThrows_RegistryAccessException(Typ public void Valid_ShouldPass_Exception() { _origin.Valid().Throws(new Exception()); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Valid(); @@ -126,7 +126,7 @@ public void Valid_ShouldPass_Exception() [TestMethod] public void Create_ShouldCall_OriginCreate() { - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); record.Create(); @@ -139,9 +139,9 @@ public void Create_ShouldCall_OriginCreate() [DataRow(typeof(IOException))] public void Create_ShouldSuppress_RegistryAccessException(Type exceptionType) { - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.When(x => x.Create()).Do(_ => throw exception); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Create(); @@ -151,8 +151,8 @@ public void Create_ShouldSuppress_RegistryAccessException(Type exceptionType) [TestMethod] public void Create_ShouldPass_Exception() { - _origin.When(x => x.Create()).Do(_ => throw new Exception()); - var record = new SafeStartupRecord(_logger, _origin); + _origin.When(x => x.Create()).Do(_ => throw new()); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Create(); @@ -162,7 +162,7 @@ public void Create_ShouldPass_Exception() [TestMethod] public void Remove_ShouldCall_OriginRemove() { - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); record.Remove(); @@ -175,9 +175,9 @@ public void Remove_ShouldCall_OriginRemove() [DataRow(typeof(IOException))] public void Remove_ShouldSuppress_RegistryAccessException(Type exceptionType) { - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.When(x => x.Remove()).Do(_ => throw exception); - var record = new SafeStartupRecord(_logger, _origin); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Remove(); @@ -187,8 +187,8 @@ public void Remove_ShouldSuppress_RegistryAccessException(Type exceptionType) [TestMethod] public void Remove_ShouldPass_Exception() { - _origin.When(x => x.Remove()).Do(_ => throw new Exception()); - var record = new SafeStartupRecord(_logger, _origin); + _origin.When(x => x.Remove()).Do(_ => throw new()); + SafeStartupRecord record = new(_logger, _origin); Action action = () => record.Remove(); diff --git a/test/ProtonVPN.Common.Test/OS/Services/ExceptionExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ExceptionExtensionsTest.cs similarity index 72% rename from test/ProtonVPN.Common.Test/OS/Services/ExceptionExtensionsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Services/ExceptionExtensionsTest.cs index c5421d8b5..8b5167c3e 100644 --- a/test/ProtonVPN.Common.Test/OS/Services/ExceptionExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ExceptionExtensionsTest.cs @@ -23,7 +23,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.OS.Services; -namespace ProtonVPN.Common.Test.OS.Services +namespace ProtonVPN.Common.Tests.OS.Services { [TestClass] public class ExceptionExtensionsTest @@ -35,9 +35,9 @@ public class ExceptionExtensionsTest public void IsServiceAlreadyRunning_ShouldBeTrue() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(ServiceAlreadyRunning)); + InvalidOperationException exception = new("", new Win32Exception(ServiceAlreadyRunning)); // Act - var result = exception.IsServiceAlreadyRunning(); + bool result = exception.IsServiceAlreadyRunning(); // Assert result.Should().BeTrue(); } @@ -46,9 +46,9 @@ public void IsServiceAlreadyRunning_ShouldBeTrue() public void IsServiceAlreadyRunning_ShouldBeFalse_WhenNativeErrorCode_IsDifferent() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(1055)); + InvalidOperationException exception = new("", new Win32Exception(1055)); // Act - var result = exception.IsServiceAlreadyRunning(); + bool result = exception.IsServiceAlreadyRunning(); // Assert result.Should().BeFalse(); } @@ -57,9 +57,9 @@ public void IsServiceAlreadyRunning_ShouldBeFalse_WhenNativeErrorCode_IsDifferen public void IsServiceAlreadyRunning_ShouldBeFalse_WhenInnerException_IsNull() { // Arrange - var exception = new InvalidOperationException(); + InvalidOperationException exception = new(); // Act - var result = exception.IsServiceAlreadyRunning(); + bool result = exception.IsServiceAlreadyRunning(); // Assert result.Should().BeFalse(); } @@ -68,9 +68,9 @@ public void IsServiceAlreadyRunning_ShouldBeFalse_WhenInnerException_IsNull() public void IsServiceNotRunning_ShouldBeTrue() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(ServiceNotRunning)); + InvalidOperationException exception = new("", new Win32Exception(ServiceNotRunning)); // Act - var result = exception.IsServiceNotRunning(); + bool result = exception.IsServiceNotRunning(); // Assert result.Should().BeTrue(); } @@ -79,9 +79,9 @@ public void IsServiceNotRunning_ShouldBeTrue() public void IsServiceNotRunning_ShouldBeFalse_WhenNativeErrorCode_IsDifferent() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(1063)); + InvalidOperationException exception = new("", new Win32Exception(1063)); // Act - var result = exception.IsServiceNotRunning(); + bool result = exception.IsServiceNotRunning(); // Assert result.Should().BeFalse(); } @@ -90,9 +90,9 @@ public void IsServiceNotRunning_ShouldBeFalse_WhenNativeErrorCode_IsDifferent() public void IsServiceNotRunning_ShouldBeFalse_WhenInnerException_IsNull() { // Arrange - var exception = new InvalidOperationException(); + InvalidOperationException exception = new(); // Act - var result = exception.IsServiceNotRunning(); + bool result = exception.IsServiceNotRunning(); // Assert result.Should().BeFalse(); } diff --git a/test/ProtonVPN.Common.Test/OS/Services/LoggingServiceTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Services/LoggingServiceTest.cs similarity index 99% rename from test/ProtonVPN.Common.Test/OS/Services/LoggingServiceTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Services/LoggingServiceTest.cs index 28f051c70..b9b131426 100644 --- a/test/ProtonVPN.Common.Test/OS/Services/LoggingServiceTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Services/LoggingServiceTest.cs @@ -30,7 +30,7 @@ using ProtonVPN.Common.Logging.Categorization.Events.AppServiceLogs; using ProtonVPN.Common.OS.Services; -namespace ProtonVPN.Common.Test.OS.Services +namespace ProtonVPN.Common.Tests.OS.Services { [TestClass] public class LoggingServiceTest diff --git a/test/ProtonVPN.Common.Test/OS/Services/ReliableServiceTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ReliableServiceTest.cs similarity index 78% rename from test/ProtonVPN.Common.Test/OS/Services/ReliableServiceTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Services/ReliableServiceTest.cs index e88c785da..2eb8b8eef 100644 --- a/test/ProtonVPN.Common.Test/OS/Services/ReliableServiceTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ReliableServiceTest.cs @@ -26,7 +26,7 @@ using ProtonVPN.Common.Abstract; using ProtonVPN.Common.OS.Services; -namespace ProtonVPN.Common.Test.OS.Services +namespace ProtonVPN.Common.Tests.OS.Services { [TestClass] public class ReliableServiceTest @@ -47,10 +47,10 @@ public void Name_ShouldBe_Origin_Name() // Arrange const string name = "My service"; _origin.Name.Returns(name); - var subject = new ReliableService(_retryPolicy, _origin); + ReliableService subject = new(_retryPolicy, _origin); // Act - var result = subject.Name; + string result = subject.Name; // Assert result.Should().Be(name); @@ -63,10 +63,10 @@ public void Running_ShouldBe_Origin_Running(bool value) { // Arrange _origin.Running().Returns(value); - var subject = new ReliableService(_retryPolicy, _origin); + ReliableService subject = new(_retryPolicy, _origin); // Act - var result = subject.Running(); + bool result = subject.Running(); // Assert result.Should().Be(value); @@ -76,16 +76,16 @@ public void Running_ShouldBe_Origin_Running(bool value) public async Task StartAsync_ShouldBe_Origin_StartAsync() { // Arrange - var expected = Result.Ok(); - var cancellationToken = new CancellationToken(); + Result expected = Result.Ok(); + CancellationToken cancellationToken = new(); _origin.StartAsync(cancellationToken).Returns(expected); _retryPolicy .ExecuteAsync(Arg.Any>>(), Arg.Any()) .Returns(args => args.Arg>>()(cancellationToken)); - var subject = new ReliableService(_retryPolicy, _origin); + ReliableService subject = new(_retryPolicy, _origin); // Act - var result = await subject.StartAsync(cancellationToken); + Result result = await subject.StartAsync(cancellationToken); // Assert result.Should().BeSameAs(expected); @@ -95,13 +95,13 @@ public async Task StartAsync_ShouldBe_Origin_StartAsync() public async Task StopAsync_ShouldBe_Origin_StopAsync() { // Arrange - var expected = Result.Ok(); - var cancellationToken = new CancellationToken(); + Result expected = Result.Ok(); + CancellationToken cancellationToken = new(); _origin.StopAsync(cancellationToken).Returns(expected); - var subject = new ReliableService(_retryPolicy, _origin); + ReliableService subject = new(_retryPolicy, _origin); // Act - var result = await subject.StopAsync(cancellationToken); + Result result = await subject.StopAsync(cancellationToken); // Assert result.Should().BeSameAs(expected); diff --git a/test/ProtonVPN.Common.Test/OS/Services/SafeServiceTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Services/SafeServiceTest.cs similarity index 70% rename from test/ProtonVPN.Common.Test/OS/Services/SafeServiceTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Services/SafeServiceTest.cs index 871c75514..9449967b6 100644 --- a/test/ProtonVPN.Common.Test/OS/Services/SafeServiceTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Services/SafeServiceTest.cs @@ -28,7 +28,7 @@ using ProtonVPN.Common.Abstract; using ProtonVPN.Common.OS.Services; -namespace ProtonVPN.Common.Test.OS.Services +namespace ProtonVPN.Common.Tests.OS.Services { [TestClass] public class SafeServiceTest @@ -50,10 +50,10 @@ public void Name_ShouldBe_Origin_Name() // Arrange const string name = "My service"; _origin.Name.Returns(name); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = subject.Name; + string result = subject.Name; // Assert result.Should().Be(name); @@ -66,10 +66,10 @@ public void Running_ShouldBe_Origin_Running(bool value) { // Arrange _origin.Running().Returns(value); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = subject.Running(); + bool result = subject.Running(); // Assert result.Should().Be(value); @@ -80,12 +80,12 @@ public void Running_ShouldBe_Origin_Running(bool value) public void Running_ShouldBeFalse_WhenOriginThrows_ExpectedException(Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); _origin.Running().Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = subject.Running(); + bool result = subject.Running(); // Assert result.Should().BeFalse(); @@ -95,9 +95,9 @@ public void Running_ShouldBeFalse_WhenOriginThrows_ExpectedException(Type except public void Running_ShouldPass_NotExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.Running().Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act Action action = () => subject.Running(); @@ -110,13 +110,13 @@ public void Running_ShouldPass_NotExpectedException() public async Task StartAsync_ShouldBe_Origin_StartAsync() { // Arrange - var expected = Result.Ok(); - var cancellationToken = new CancellationToken(); + Result expected = Result.Ok(); + CancellationToken cancellationToken = new(); _origin.StartAsync(cancellationToken).Returns(expected); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StartAsync(cancellationToken); + Result result = await subject.StartAsync(cancellationToken); // Assert result.Should().BeSameAs(expected); @@ -126,13 +126,13 @@ public async Task StartAsync_ShouldBe_Origin_StartAsync() public async Task StartAsync_ShouldBeSuccess_WhenOriginThrows_ServiceAlreadyRunning() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(ServiceAlreadyRunning)); - var cancellationToken = CancellationToken.None; + InvalidOperationException exception = new("", new Win32Exception(ServiceAlreadyRunning)); + CancellationToken cancellationToken = CancellationToken.None; _origin.StartAsync(cancellationToken).Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StartAsync(cancellationToken); + Result result = await subject.StartAsync(cancellationToken); // Assert result.Success.Should().BeTrue(); @@ -145,13 +145,13 @@ public async Task StartAsync_ShouldBeSuccess_WhenOriginThrows_ServiceAlreadyRunn public async Task StartAsync_ShouldBeFailure_WhenOriginThrows_ExpectedException(Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); - var cancellationToken = CancellationToken.None; + Exception exception = (Exception)Activator.CreateInstance(exceptionType); + CancellationToken cancellationToken = CancellationToken.None; _origin.StartAsync(cancellationToken).Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StartAsync(cancellationToken); + Result result = await subject.StartAsync(cancellationToken); // Assert result.Failure.Should().BeTrue(); @@ -162,13 +162,13 @@ public async Task StartAsync_ShouldBeFailure_WhenOriginThrows_ExpectedException( public async Task StopAsync_ShouldBe_Origin_StopAsync() { // Arrange - var expected = Result.Ok(); - var cancellationToken = new CancellationToken(); + Result expected = Result.Ok(); + CancellationToken cancellationToken = new(); _origin.StopAsync(cancellationToken).Returns(expected); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StopAsync(cancellationToken); + Result result = await subject.StopAsync(cancellationToken); // Assert result.Should().BeSameAs(expected); @@ -178,13 +178,13 @@ public async Task StopAsync_ShouldBe_Origin_StopAsync() public async Task StopAsync_ShouldBeSuccess_WhenOriginThrows_ServiceNotRunning() { // Arrange - var exception = new InvalidOperationException("", new Win32Exception(ServiceNotRunning)); - var cancellationToken = CancellationToken.None; + InvalidOperationException exception = new("", new Win32Exception(ServiceNotRunning)); + CancellationToken cancellationToken = CancellationToken.None; _origin.StopAsync(cancellationToken).Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StopAsync(cancellationToken); + Result result = await subject.StopAsync(cancellationToken); // Assert result.Success.Should().BeTrue(); @@ -197,13 +197,13 @@ public async Task StopAsync_ShouldBeSuccess_WhenOriginThrows_ServiceNotRunning() public async Task StopAsync_ShouldBeFailure_WhenOriginThrows_ExpectedException(Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); - var cancellationToken = CancellationToken.None; + Exception exception = (Exception)Activator.CreateInstance(exceptionType); + CancellationToken cancellationToken = CancellationToken.None; _origin.StopAsync(cancellationToken).Throws(exception); - var subject = new SafeService(_origin); + SafeService subject = new(_origin); // Act - var result = await subject.StopAsync(cancellationToken); + Result result = await subject.StopAsync(cancellationToken); // Assert result.Failure.Should().BeTrue(); diff --git a/test/ProtonVPN.Common.Test/OS/Services/ServiceRetryPolicyTest.cs b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ServiceRetryPolicyTest.cs similarity index 74% rename from test/ProtonVPN.Common.Test/OS/Services/ServiceRetryPolicyTest.cs rename to src/Tests/ProtonVPN.Common.Tests/OS/Services/ServiceRetryPolicyTest.cs index 3570e443e..56318c377 100644 --- a/test/ProtonVPN.Common.Test/OS/Services/ServiceRetryPolicyTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/OS/Services/ServiceRetryPolicyTest.cs @@ -25,9 +25,9 @@ using ProtonVPN.Common.Abstract; using ProtonVPN.Common.Extensions; using ProtonVPN.Common.OS.Services; -using ProtonVPN.Test.Common.Breakpoints; +using ProtonVPN.Tests.Common.Breakpoints; -namespace ProtonVPN.Common.Test.OS.Services +namespace ProtonVPN.Common.Tests.OS.Services { [TestClass] public class ServiceRetryPolicyTest @@ -38,11 +38,11 @@ public class ServiceRetryPolicyTest public async Task ExecuteAsync_ShouldBe_ActionResult() { // Arrange - var expected = Result.Ok(); - var policy = new ServiceRetryPolicy(2, TimeSpan.Zero); + Result expected = Result.Ok(); + ServiceRetryPolicy policy = new(2, TimeSpan.Zero); // Act - var result = await policy.ExecuteAsync( ct => Task.FromResult(expected), CancellationToken.None); + Result result = await policy.ExecuteAsync( ct => Task.FromResult(expected), CancellationToken.None); // Assert result.Should().Be(expected); @@ -52,10 +52,10 @@ public async Task ExecuteAsync_ShouldBe_ActionResult() public async Task ExecuteAsync_ShouldBeFailure_WhenActionReturnsFailure() { // Arrange - var policy = new ServiceRetryPolicy(0, TimeSpan.Zero); + ServiceRetryPolicy policy = new(0, TimeSpan.Zero); // Act - var result = await policy.ExecuteAsync(ct => Task.FromResult(Result.Fail()), CancellationToken.None); + Result result = await policy.ExecuteAsync(ct => Task.FromResult(Result.Fail()), CancellationToken.None); // Assert result.Failure.Should().BeTrue(); @@ -66,8 +66,8 @@ public async Task ExecuteAsync_ShouldCallAction_RetryCountPlusOneTime_WhenAction { // Arrange const int retryCount = 2; - var timesCalled = 0; - var policy = new ServiceRetryPolicy(retryCount, TimeSpan.Zero); + int timesCalled = 0; + ServiceRetryPolicy policy = new(retryCount, TimeSpan.Zero); // Act await policy.ExecuteAsync( @@ -86,14 +86,14 @@ await policy.ExecuteAsync( public async Task ExecuteAsync_ShouldCancelAction_WhenCancellationToken_IsCancelled() { // Arrange - var policy = new ServiceRetryPolicy(0, TimeSpan.Zero); - using (var cancellationSource = new CancellationTokenSource()) - using (var breakpoint = new Breakpoint()) + ServiceRetryPolicy policy = new(0, TimeSpan.Zero); + using (CancellationTokenSource cancellationSource = new()) + using (Breakpoint breakpoint = new()) { - var cancelled = false; + bool cancelled = false; // Act - var task = policy.ExecuteAsync( + Task task = policy.ExecuteAsync( async ct => { // ReSharper disable once AccessToDisposedClosure @@ -104,10 +104,10 @@ public async Task ExecuteAsync_ShouldCancelAction_WhenCancellationToken_IsCancel }, cancellationSource.Token); - var hit = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + BreakpointHit hit = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); cancellationSource.Cancel(); hit.Continue(); - var result = await task.TimeoutAfter(TestTimeout); + Result result = await task.TimeoutAfter(TestTimeout); // Assert result.Success.Should().BeFalse(); diff --git a/test/ProtonVPN.Common.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Common.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.Common.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Common.Tests/Properties/AssemblyInfo.cs index 607cc2a1e..9318df5d6 100644 --- a/test/ProtonVPN.Common.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Common.Test")] +[assembly: AssemblyTitle("ProtonVPN.Common.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Common.Test")] +[assembly: AssemblyProduct("ProtonVPN.Common.Tests")] [assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.Common.Tests/ProtonVPN.Common.Tests.csproj b/src/Tests/ProtonVPN.Common.Tests/ProtonVPN.Common.Tests.csproj new file mode 100644 index 000000000..85fd2e164 --- /dev/null +++ b/src/Tests/ProtonVPN.Common.Tests/ProtonVPN.Common.Tests.csproj @@ -0,0 +1,148 @@ + + + + + Debug + AnyCPU + {5F2931B6-9A77-4F94-80CD-BC9B9A0C64BF} + Library + Properties + ProtonVPN.Common.Tests + ProtonVPN.Common.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + {03b8e43c-5680-4803-a745-0a104fe6620c} + ProtonVPN.Common + + + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} + ProtonVPN.Crypto + + + {A0DA4200-6643-4F2C-8450-65B8CE8A5576} + ProtonVPN.Tests.Common + + + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 13.0.1 + + + 4.3.0 + + + 7.2.0 + + + 4.3.4 + + + 4.3.1 + + + 5.0.0 + + + 4.5.4 + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/Storage/CollectionStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Storage/CollectionStorageTest.cs similarity index 82% rename from test/ProtonVPN.Common.Test/Storage/CollectionStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Storage/CollectionStorageTest.cs index 8b3e291bc..fe63a96a9 100644 --- a/test/ProtonVPN.Common.Test/Storage/CollectionStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Storage/CollectionStorageTest.cs @@ -25,7 +25,7 @@ using NSubstitute; using ProtonVPN.Common.Storage; -namespace ProtonVPN.Common.Test.Storage +namespace ProtonVPN.Common.Tests.Storage { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -53,12 +53,12 @@ public void CollectionStorage_ShouldThrow_WhenOrigin_IsNull() public void GetAll_ShouldBeEquivalentTo_OriginGet() { // Arrange - var expected = new[] {77, 256}; + int[] expected = new[] {77, 256}; _origin.Get().Returns(expected); - var storage = new CollectionStorage(_origin); + CollectionStorage storage = new CollectionStorage(_origin); // Act - var result = storage.GetAll(); + IReadOnlyCollection result = storage.GetAll(); // Assert result.Should().BeEquivalentTo(expected); @@ -69,10 +69,10 @@ public void GetAll_ShouldBeEmpty_WhenOrigin_Get_IsNull() { // Arrange _origin.Get().Returns((IEnumerable)null); - var storage = new CollectionStorage(_origin); + CollectionStorage storage = new CollectionStorage(_origin); // Act - var result = storage.GetAll(); + IReadOnlyCollection result = storage.GetAll(); // Assert result.Should().NotBeNull().And.BeEmpty(); @@ -82,8 +82,8 @@ public void GetAll_ShouldBeEmpty_WhenOrigin_Get_IsNull() public void SetAll_ShouldCall_Origin_Set() { // Arrange - var value = new[] { 324, 87, 132 }; - var storage = new CollectionStorage(_origin); + int[] value = new[] { 324, 87, 132 }; + CollectionStorage storage = new CollectionStorage(_origin); // Act storage.SetAll(value); diff --git a/test/ProtonVPN.Common.Test/Storage/FileStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Storage/FileStorageTest.cs similarity index 97% rename from test/ProtonVPN.Common.Test/Storage/FileStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Storage/FileStorageTest.cs index ba685fd57..c95cf0ca0 100644 --- a/test/ProtonVPN.Common.Test/Storage/FileStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Storage/FileStorageTest.cs @@ -26,9 +26,9 @@ using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Storage; using ProtonVPN.Common.Text.Serialization; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; -namespace ProtonVPN.Common.Test.Storage +namespace ProtonVPN.Common.Tests.Storage { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -191,7 +191,7 @@ public void IsExpectedException_ShouldBe_SerializerFactory_Serializer_IsExpected // Arrange Exception exception = new(); ((IThrowsExpectedExceptions)_serializer).IsExpectedException(exception).Returns(expected); - var storage = new FileStorage(_serializerFactory, "kmb"); + FileStorage storage = new FileStorage(_serializerFactory, "kmb"); // Act bool result = storage.IsExpectedException(exception); diff --git a/test/ProtonVPN.Common.Test/Storage/LoggingStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Storage/LoggingStorageTest.cs similarity index 84% rename from test/ProtonVPN.Common.Test/Storage/LoggingStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Storage/LoggingStorageTest.cs index 936fdff5f..f778d1593 100644 --- a/test/ProtonVPN.Common.Test/Storage/LoggingStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Storage/LoggingStorageTest.cs @@ -27,7 +27,7 @@ using ProtonVPN.Common.Logging.Categorization.Events.AppLogs; using ProtonVPN.Common.Storage; -namespace ProtonVPN.Common.Test.Storage +namespace ProtonVPN.Common.Tests.Storage { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -68,7 +68,7 @@ public void LoggingStorage_ShouldThrow_WhenOriginIsNull() public void LoggingStorage_ShouldThrow_WhenOriginDoesNotImplement_IThrowsExpectedExceptions() { // Arrange - var origin = Substitute.For>(); + IStorage origin = Substitute.For>(); // Act Action action = () => new LoggingStorage(_logger, origin); @@ -83,7 +83,7 @@ public void Get_ShouldBe_OriginGet() // Arrange const string expected = "John Doe"; _origin.Get().Returns(expected); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act string value = storage.Get(); @@ -96,11 +96,11 @@ public void Get_ShouldBe_OriginGet() public void Get_ShouldLog_WhenOriginThrows_ExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Get()).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(true); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act try @@ -119,10 +119,10 @@ public void Get_ShouldLog_WhenOriginThrows_ExpectedException() public void Get_ShouldPass_ExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Get()).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(true); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act Action action = () => storage.Get(); @@ -135,9 +135,9 @@ public void Get_ShouldPass_ExpectedException() public void Get_ShouldPass_UnexpectedException() { // Arrange - _origin.When(x => x.Get()).Do(_ => throw new Exception()); + _origin.When(x => x.Get()).Do(_ => throw new()); ((IThrowsExpectedExceptions)_origin).IsExpectedException(Arg.Any()).Returns(false); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act Action action = () => storage.Get(); @@ -151,7 +151,7 @@ public void Set_ShouldCall_OriginSet() { // Arrange const string value = "John Doe Set"; - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act storage.Set(value); @@ -164,10 +164,10 @@ public void Set_ShouldCall_OriginSet() public void Set_ShouldLog_UnexpectedException() { // Arrange - _origin.When(x => x.Set(Arg.Any())).Do(_ => throw new Exception()); + _origin.When(x => x.Set(Arg.Any())).Do(_ => throw new()); ((IThrowsExpectedExceptions)_origin).IsExpectedException(Arg.Any()).Returns(false); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act try @@ -186,11 +186,11 @@ public void Set_ShouldLog_UnexpectedException() public void Set_ShouldPass_ExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Set(Arg.Any())).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(true); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act Action action = () => storage.Set("ABC"); @@ -203,9 +203,9 @@ public void Set_ShouldPass_ExpectedException() public void Set_ShouldPass_UnexpectedException() { // Arrange - _origin.When(x => x.Set(Arg.Any())).Do(_ => throw new Exception()); + _origin.When(x => x.Set(Arg.Any())).Do(_ => throw new()); ((IThrowsExpectedExceptions)_origin).IsExpectedException(Arg.Any()).Returns(false); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act Action action = () => storage.Set("ABC"); @@ -220,10 +220,10 @@ public void Set_ShouldPass_UnexpectedException() public void IsExpectedException_ShouldBe_Origin_IsExpectedException(bool expected) { // Arrange - var exception = new Exception(); + Exception exception = new(); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(expected); - var storage = new LoggingStorage(_logger, _origin); + LoggingStorage storage = new LoggingStorage(_logger, _origin); // Act bool result = storage.IsExpectedException(exception); diff --git a/test/ProtonVPN.Common.Test/Storage/SafeStorageTest.cs b/src/Tests/ProtonVPN.Common.Tests/Storage/SafeStorageTest.cs similarity index 83% rename from test/ProtonVPN.Common.Test/Storage/SafeStorageTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Storage/SafeStorageTest.cs index b8489eb14..49981f295 100644 --- a/test/ProtonVPN.Common.Test/Storage/SafeStorageTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Storage/SafeStorageTest.cs @@ -25,7 +25,7 @@ using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Storage; -namespace ProtonVPN.Common.Test.Storage +namespace ProtonVPN.Common.Tests.Storage { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -53,7 +53,7 @@ public void SafeStorage_ShouldThrow_WhenOriginIsNull() public void SafeStorage_ShouldThrow_WhenOriginDoesNotImplement_IThrowsExpectedExceptions() { // Arrange - var origin = Substitute.For>(); + IStorage origin = Substitute.For>(); // Act Action action = () => new SafeStorage(origin); @@ -68,10 +68,10 @@ public void Get_ShouldBe_OriginGet() // Arrange const string expected = "John Doe"; _origin.Get().Returns(expected); - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act - var result = storage.Get(); + string result = storage.Get(); // Assert result.Should().Be(expected); @@ -81,14 +81,14 @@ public void Get_ShouldBe_OriginGet() public void Get_ShouldBeNull_WhenOriginThrows_ExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Get()).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(true); - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act - var result = storage.Get(); + string result = storage.Get(); // Assert result.Should().BeNull(); @@ -98,9 +98,9 @@ public void Get_ShouldBeNull_WhenOriginThrows_ExpectedException() public void Get_ShouldPass_UnexpectedException() { // Arrange - _origin.When(x => x.Get()).Do(_ => throw new Exception()); + _origin.When(x => x.Get()).Do(_ => throw new()); ((IThrowsExpectedExceptions)_origin).IsExpectedException(Arg.Any()).Returns(false); - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act Action action = () => storage.Get(); @@ -114,7 +114,7 @@ public void Set_ShouldCall_OriginSet() { // Arrange const string value = "John Doe Set"; - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act storage.Set(value); @@ -127,11 +127,11 @@ public void Set_ShouldCall_OriginSet() public void Set_ShouldIgnore_ExpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Set(Arg.Any())).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(exception).Returns(true); - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act Action action = () => storage.Set("ABC"); @@ -144,11 +144,11 @@ public void Set_ShouldIgnore_ExpectedException() public void Set_ShouldPass_UnexpectedException() { // Arrange - var exception = new Exception(); + Exception exception = new(); _origin.When(x => x.Set(Arg.Any())).Do(_ => throw exception); ((IThrowsExpectedExceptions)_origin).IsExpectedException(Arg.Any()).Returns(false); - var storage = new SafeStorage(_origin); + SafeStorage storage = new SafeStorage(_origin); // Act Action action = () => storage.Set("ABC"); diff --git a/test/ProtonVPN.Common.Test/TestData/Config.json b/src/Tests/ProtonVPN.Common.Tests/TestData/Config.json similarity index 100% rename from test/ProtonVPN.Common.Test/TestData/Config.json rename to src/Tests/ProtonVPN.Common.Tests/TestData/Config.json diff --git a/test/ProtonVPN.Common.Test/TestData/Test.json b/src/Tests/ProtonVPN.Common.Tests/TestData/Test.json similarity index 100% rename from test/ProtonVPN.Common.Test/TestData/Test.json rename to src/Tests/ProtonVPN.Common.Tests/TestData/Test.json diff --git a/test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerFactoryTest.cs b/src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerFactoryTest.cs similarity index 82% rename from test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerFactoryTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerFactoryTest.cs index 8060385f4..86af49579 100644 --- a/test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerFactoryTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerFactoryTest.cs @@ -21,7 +21,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Text.Serialization; -namespace ProtonVPN.Common.Test.Text.Serialization +namespace ProtonVPN.Common.Tests.Text.Serialization { [TestClass] public class JsonSerializerFactoryTest @@ -30,10 +30,10 @@ public class JsonSerializerFactoryTest public void Serializer_Should_NotBeNull() { // Arrange - var factory = new JsonSerializerFactory(); + JsonSerializerFactory factory = new(); // Act - var result = factory.Serializer(); + ITextSerializer result = factory.Serializer(); // Assert result.Should().NotBeNull(); @@ -43,10 +43,10 @@ public void Serializer_Should_NotBeNull() public void Serializer_ShouldBe_JsonSerializer() { // Arrange - var factory = new JsonSerializerFactory(); + JsonSerializerFactory factory = new(); // Act - var result = factory.Serializer(); + ITextSerializer result = factory.Serializer(); // Assert result.Should().BeOfType>(); diff --git a/test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerTest.cs b/src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerTest.cs similarity index 72% rename from test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerTest.cs index bef9ea714..eb41b4b0d 100644 --- a/test/ProtonVPN.Common.Test/Text/Serialization/JsonSerializerTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Text/Serialization/JsonSerializerTest.cs @@ -24,7 +24,7 @@ using Newtonsoft.Json; using ProtonVPN.Common.Text.Serialization; -namespace ProtonVPN.Common.Test.Text.Serialization +namespace ProtonVPN.Common.Tests.Text.Serialization { [TestClass] public class JsonSerializerTest @@ -33,11 +33,11 @@ public class JsonSerializerTest public void Deserialize_ShouldHave_ExpectedPropertyValues() { // Arrange - var reader = new StringReader("{\"Number\":199,\"Name\":\"Super Mario\"}"); - var serializer = new JsonSerializer(); + StringReader reader = new("{\"Number\":199,\"Name\":\"Super Mario\"}"); + JsonSerializer serializer = new JsonSerializer(); // Act - var result = serializer.Deserialize(reader); + SampleContract result = serializer.Deserialize(reader); // Assert result.Number.Should().Be(199); @@ -48,8 +48,8 @@ public void Deserialize_ShouldHave_ExpectedPropertyValues() public void Deserialize_ShouldThrow_ExpectedException_WhenNotCorrectJson() { // Arrange - var reader = new StringReader("{Segment"); - var serializer = new JsonSerializer(); + StringReader reader = new("{Segment"); + JsonSerializer serializer = new JsonSerializer(); // Act Action action = () => serializer.Deserialize(reader); @@ -62,15 +62,15 @@ public void Deserialize_ShouldThrow_ExpectedException_WhenNotCorrectJson() public void Serialize_ShouldSerialize_ToJson() { // Arrange - var value = new SampleContract { Number = 34, Name = "ZgD" }; - var serializer = new JsonSerializer(); - var writer = new StringWriter(); + SampleContract value = new() { Number = 34, Name = "ZgD" }; + JsonSerializer serializer = new JsonSerializer(); + StringWriter writer = new(); // Act serializer.Serialize(value, writer); // Assert - var result = writer.GetStringBuilder().ToString(); + string result = writer.GetStringBuilder().ToString(); result.Should().Be("{\"Number\":34,\"Name\":\"ZgD\"}"); } @@ -82,11 +82,11 @@ public void Serialize_ShouldSerialize_ToJson() public void IsExpectedException_ShouldBeTrue_WhenExpectedException(bool expected, Type exceptionType) { // Arrange - var exception = (Exception)Activator.CreateInstance(exceptionType); - var serializer = new JsonSerializer(); + Exception exception = (Exception)Activator.CreateInstance(exceptionType); + JsonSerializer serializer = new JsonSerializer(); // Act - var result = serializer.IsExpectedException(exception); + bool result = serializer.IsExpectedException(exception); // Assert result.Should().Be(expected); diff --git a/test/ProtonVPN.Common.Test/Threading/CoalescingTaskQueueTest.cs b/src/Tests/ProtonVPN.Common.Tests/Threading/CoalescingTaskQueueTest.cs similarity index 67% rename from test/ProtonVPN.Common.Test/Threading/CoalescingTaskQueueTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Threading/CoalescingTaskQueueTest.cs index b941fa84f..de3080265 100644 --- a/test/ProtonVPN.Common.Test/Threading/CoalescingTaskQueueTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Threading/CoalescingTaskQueueTest.cs @@ -25,9 +25,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Extensions; using ProtonVPN.Common.Threading; -using ProtonVPN.Test.Common.Breakpoints; +using ProtonVPN.Tests.Common.Breakpoints; -namespace ProtonVPN.Common.Test.Threading +namespace ProtonVPN.Common.Tests.Threading { [TestClass] public class CoalescingTaskQueueTest @@ -39,10 +39,10 @@ public async Task Enqueue_ShouldSchedule_NewTask() { // Arrange const int expected = 397; - var queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); // Act - var result = await queue.Enqueue(() => expected, expected); + int result = await queue.Enqueue(() => expected, expected); // Assert result.Should().Be(expected); @@ -53,9 +53,9 @@ public async Task Enqueue_ShouldJoin_RunningTask() { // Arrange const int expected = 147; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); async Task TestAction(int result) { @@ -65,10 +65,10 @@ async Task TestAction(int result) } // Act - var task1 = queue.Enqueue(() => TestAction(expected), expected); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task1 = queue.Enqueue(() => TestAction(expected), expected); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(() => TestAction(328), 328); + Task task2 = queue.Enqueue(() => TestAction(328), 328); hit1.Continue(); @@ -84,9 +84,9 @@ public async Task Enqueue_ShouldQueue_PendingTask() // Arrange const int expected1 = 291; const int expected2 = 872; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); async Task TestAction(int result) { @@ -96,8 +96,8 @@ async Task TestAction(int result) } // Act - var task1 = queue.Enqueue(() => TestAction(expected1), expected1); - var task2 = queue.Enqueue(() => TestAction(expected2), expected2); + Task task1 = queue.Enqueue(() => TestAction(expected1), expected1); + Task task2 = queue.Enqueue(() => TestAction(expected2), expected2); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -113,9 +113,9 @@ public async Task Enqueue_ShouldJoin_PendingTask() // Arrange const int expected1 = 3905; const int expected2 = 4487; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => newArg > 0 ? CoalesceDecision.None : CoalesceDecision.Join); async Task TestAction(int result, CancellationToken ct) @@ -127,11 +127,11 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(expected1, ct), expected1); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(expected2, ct), expected2); + Task task1 = queue.Enqueue(ct => TestAction(expected1, ct), expected1); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(expected2, ct), expected2); - var task3 = queue.Enqueue(ct => TestAction(-15, ct), -15); + Task task3 = queue.Enqueue(ct => TestAction(-15, ct), -15); hit1.Continue(); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -148,9 +148,9 @@ public async Task Enqueue_ShouldCancel_RunningTask() { // Arrange const int expected2 = 4487; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Cancel); async Task TestAction(int result, CancellationToken ct) @@ -162,9 +162,9 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(101, ct), 101); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(expected2, ct), expected2); + Task task1 = queue.Enqueue(ct => TestAction(101, ct), 101); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(expected2, ct), expected2); hit1.Continue(); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -180,9 +180,9 @@ public async Task Enqueue_ShouldCancel_PendingTask() // Arrange const int expected1 = 657; const int expected3 = 134; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); async Task TestAction(int result, CancellationToken ct) { @@ -193,10 +193,10 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(expected1, ct), expected1); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(101, ct), 101); - var task3 = queue.Enqueue(ct => TestAction(expected3, ct), expected3); + Task task1 = queue.Enqueue(ct => TestAction(expected1, ct), expected1); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(101, ct), 101); + Task task3 = queue.Enqueue(ct => TestAction(expected3, ct), expected3); hit1.Continue(); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -212,9 +212,9 @@ public async Task Enqueue_ShouldCancel_PendingAndRunningTasks() { // Arrange const int expected3 = 3617; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => newArg == expected3 ? CoalesceDecision.Cancel : CoalesceDecision.None); async Task TestAction(int result, CancellationToken ct) @@ -226,10 +226,10 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(202, ct), 202); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(303, ct), 303); - var task3 = queue.Enqueue(ct => TestAction(expected3, ct), expected3); + Task task1 = queue.Enqueue(ct => TestAction(202, ct), 202); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(303, ct), 303); + Task task3 = queue.Enqueue(ct => TestAction(expected3, ct), expected3); hit1.Continue(); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -245,9 +245,9 @@ public async Task Enqueue_ShouldNotJoin_CancelledRunningTask() { // Arrange const int expected = 6874; - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); async Task TestAction(int result, CancellationToken ct) @@ -260,9 +260,9 @@ async Task TestAction(int result, CancellationToken ct) // Act _ = queue.Enqueue(ct => TestAction(1981, ct), 1981); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); queue.Cancel(); - var task2 = queue.Enqueue(ct => TestAction(expected, ct), expected); + Task task2 = queue.Enqueue(ct => TestAction(expected, ct), expected); hit1.Continue(); await breakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout); @@ -275,7 +275,7 @@ async Task TestAction(int result, CancellationToken ct) public void Cancel_ShouldSucceed_WhenNoTasksRunning() { // Arrange - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); // Act @@ -289,9 +289,9 @@ public void Cancel_ShouldSucceed_WhenNoTasksRunning() public async Task Cancel_ShouldCancel_RunningTask() { // Arrange - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.Join); async Task TestAction(int result, CancellationToken ct) @@ -303,8 +303,8 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task = queue.Enqueue(ct => TestAction(9874, ct), 9874); - var hit = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task = queue.Enqueue(ct => TestAction(9874, ct), 9874); + BreakpointHit hit = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); queue.Cancel(); hit.Continue(); await Task.WhenAny(task); @@ -318,9 +318,9 @@ async Task TestAction(int result, CancellationToken ct) public async Task Cancel_ShouldCancel_PendingAndRunningTasks() { // Arrange - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); async Task TestAction(int result, CancellationToken ct) @@ -332,9 +332,9 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(6517, ct), 6517); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(101, ct), 101); + Task task1 = queue.Enqueue(ct => TestAction(6517, ct), 6517); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(101, ct), 101); queue.Cancel(); @@ -351,9 +351,9 @@ async Task TestAction(int result, CancellationToken ct) public async Task Cancel_ShouldCancel_PendingAndRunningTasks_UnderConcurrency() { // Arrange - using (var breakpoint = new Breakpoint()) + using (Breakpoint breakpoint = new()) { - var queue = new CoalescingTaskQueue((newArg, arg, running) => + CoalescingTaskQueue queue = new CoalescingTaskQueue((newArg, arg, running) => CoalesceDecision.None); async Task TestAction(int result, CancellationToken ct) @@ -365,13 +365,13 @@ async Task TestAction(int result, CancellationToken ct) } // Act - var task1 = queue.Enqueue(ct => TestAction(6517, ct), 6517); - var hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); - var task2 = queue.Enqueue(ct => TestAction(101, ct), 101); + Task task1 = queue.Enqueue(ct => TestAction(6517, ct), 6517); + BreakpointHit hit1 = await breakpoint.WaitForHit().TimeoutAfter(TestTimeout); + Task task2 = queue.Enqueue(ct => TestAction(101, ct), 101); - var cancelTasks = Enumerable.Range(1, 10) - .Select(i => Task.Run(() => queue.Cancel())) - .ToArray(); + Task[] cancelTasks = Enumerable.Range(1, 10) + .Select(i => Task.Run(() => queue.Cancel())) + .ToArray(); await Task.WhenAny(cancelTasks); hit1.Continue(); diff --git a/test/ProtonVPN.Common.Test/Threading/TaskCompletionSourceExtensionsTest.cs b/src/Tests/ProtonVPN.Common.Tests/Threading/TaskCompletionSourceExtensionsTest.cs similarity index 79% rename from test/ProtonVPN.Common.Test/Threading/TaskCompletionSourceExtensionsTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Threading/TaskCompletionSourceExtensionsTest.cs index ffa05c474..bf038d622 100644 --- a/test/ProtonVPN.Common.Test/Threading/TaskCompletionSourceExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Threading/TaskCompletionSourceExtensionsTest.cs @@ -24,7 +24,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Threading; -namespace ProtonVPN.Common.Test.Threading +namespace ProtonVPN.Common.Tests.Threading { [TestClass] public class TaskCompletionSourceExtensionsTest @@ -34,7 +34,7 @@ public async Task Wrap_ShouldSetResult() { // Arrange const int expected = 157; - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); // Act await tcs.Wrap(() => Task.FromResult(expected)); // Assert @@ -45,7 +45,7 @@ public async Task Wrap_ShouldSetResult() public async Task Wrap_ShouldSetCanceled_WhenFunction_IsCancelledAsync() { // Arrange - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); // Act await tcs.Wrap(() => throw new OperationCanceledException()); // Assert @@ -56,9 +56,9 @@ public async Task Wrap_ShouldSetCanceled_WhenFunction_IsCancelledAsync() public async Task Wrap_ShouldSetCanceled_WhenFunction_IsCanceled() { // Arrange - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); // Act - await tcs.Wrap(() => Task.FromCanceled(new CancellationToken(true))); + await tcs.Wrap(() => Task.FromCanceled(new(true))); // Assert tcs.Task.IsCanceled.Should().BeTrue(); } @@ -67,9 +67,9 @@ public async Task Wrap_ShouldSetCanceled_WhenFunction_IsCanceled() public async Task Wrap_ShouldSetException_WhenFunction_Throws() { // Arrange - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); // Act - await tcs.Wrap(() => throw new Exception()); + await tcs.Wrap(() => throw new()); // Assert tcs.Task.IsFaulted.Should().BeTrue(); } @@ -78,9 +78,9 @@ public async Task Wrap_ShouldSetException_WhenFunction_Throws() public async Task Wrap_ShouldSetException_WhenFunction_ThrowsAsync() { // Arrange - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); // Act - await tcs.Wrap(() => Task.FromException(new Exception())); + await tcs.Wrap(() => Task.FromException(new())); // Assert tcs.Task.IsFaulted.Should().BeTrue(); } diff --git a/test/ProtonVPN.Common.Test/Vpn/VpnConfigTest.cs b/src/Tests/ProtonVPN.Common.Tests/Vpn/VpnConfigTest.cs similarity index 78% rename from test/ProtonVPN.Common.Test/Vpn/VpnConfigTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Vpn/VpnConfigTest.cs index abaab7a47..b5274c7b8 100644 --- a/test/ProtonVPN.Common.Test/Vpn/VpnConfigTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Vpn/VpnConfigTest.cs @@ -24,7 +24,7 @@ using ProtonVPN.Common.Networking; using ProtonVPN.Common.Vpn; -namespace ProtonVPN.Common.Test.Vpn +namespace ProtonVPN.Common.Tests.Vpn { [TestClass] public class VpnConfigTest @@ -32,7 +32,7 @@ public class VpnConfigTest [TestMethod] public void VpnConfig_ShouldThrow_WhenPortIsNotValid() { - Dictionary> portConfig = GetPortConfig(new List + Dictionary> portConfig = GetPortConfig(new() { 1, 2, @@ -44,7 +44,7 @@ public void VpnConfig_ShouldThrow_WhenPortIsNotValid() }); // Act - Action action = () => new VpnConfig(new VpnConfigParameters {Ports = portConfig}); + Action action = () => new VpnConfig(new() {Ports = portConfig}); // Assert action.Should().Throw(); @@ -53,13 +53,13 @@ public void VpnConfig_ShouldThrow_WhenPortIsNotValid() [TestMethod] public void VpnConfig_ShouldThrow_WhenDnsIsNotValid() { - Dictionary> portConfig = GetPortConfig(new List {1, 2, 3}); + Dictionary> portConfig = GetPortConfig(new() {1, 2, 3}); - var customDns = new List {"1.1.1.1", "8.8.8.8", "--invalid-ip",}; + List customDns = new List {"1.1.1.1", "8.8.8.8", "--invalid-ip",}; // Act Action action = () => - new VpnConfig(new VpnConfigParameters {Ports = portConfig, CustomDns = customDns}); + new VpnConfig(new() {Ports = portConfig, CustomDns = customDns}); // Assert action.Should().Throw(); @@ -67,7 +67,7 @@ public void VpnConfig_ShouldThrow_WhenDnsIsNotValid() private Dictionary> GetPortConfig(List ports) { - return new Dictionary> {{VpnProtocol.OpenVpnTcp, ports}}; + return new() {{VpnProtocol.OpenVpnTcp, ports}}; } } } \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/Vpn/VpnHostTest.cs b/src/Tests/ProtonVPN.Common.Tests/Vpn/VpnHostTest.cs similarity index 90% rename from test/ProtonVPN.Common.Test/Vpn/VpnHostTest.cs rename to src/Tests/ProtonVPN.Common.Tests/Vpn/VpnHostTest.cs index 0286bb219..9012de7f2 100644 --- a/test/ProtonVPN.Common.Test/Vpn/VpnHostTest.cs +++ b/src/Tests/ProtonVPN.Common.Tests/Vpn/VpnHostTest.cs @@ -23,7 +23,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Vpn; -namespace ProtonVPN.Common.Test.Vpn +namespace ProtonVPN.Common.Tests.Vpn { [TestClass] [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] @@ -34,7 +34,7 @@ public void Name_ShouldBe_Name() { // Arrange const string expected = "server-1.protonvpn.com"; - var host = new VpnHost(expected, "127.0.0.1", string.Empty, null, string.Empty); + VpnHost host = new(expected, "127.0.0.1", string.Empty, null, string.Empty); // Act string result = host.Name; @@ -48,7 +48,7 @@ public void Ip_ShouldBe_Ip() { // Arrange const string expected = "44.55.66.77"; - var host = new VpnHost("server-1.protonvpn.com", expected, string.Empty, null, string.Empty); + VpnHost host = new("server-1.protonvpn.com", expected, string.Empty, null, string.Empty); // Act string result = host.Ip; @@ -74,7 +74,7 @@ public void IsEmpty_ShouldBeTrue_WhenDefault() public void IsEmpty_ShouldBeTrue_WhenNew() { // Arrange - var host = new VpnHost("name.com", "0.0.0.0", string.Empty, null, string.Empty); + VpnHost host = new("name.com", "0.0.0.0", string.Empty, null, string.Empty); // Act bool result = host.IsEmpty(); diff --git a/src/Tests/ProtonVPN.Common.Tests/app.config b/src/Tests/ProtonVPN.Common.Tests/app.config new file mode 100644 index 000000000..f10b4ac05 --- /dev/null +++ b/src/Tests/ProtonVPN.Common.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Core.Test/Auth/AuthCertificateManagerTest.cs b/src/Tests/ProtonVPN.Core.Tests/Auth/AuthCertificateManagerTest.cs similarity index 99% rename from test/ProtonVPN.Core.Test/Auth/AuthCertificateManagerTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Auth/AuthCertificateManagerTest.cs index 996b8b7a4..091056345 100644 --- a/test/ProtonVPN.Core.Test/Auth/AuthCertificateManagerTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Auth/AuthCertificateManagerTest.cs @@ -33,7 +33,7 @@ using ProtonVPN.Core.Auth; using ProtonVPN.Core.Settings; -namespace ProtonVPN.Core.Test.Auth +namespace ProtonVPN.Core.Tests.Auth { [TestClass] public class AuthCertificateManagerTest diff --git a/test/ProtonVPN.Core.Test/Auth/MockOfAuthKeyManager.cs b/src/Tests/ProtonVPN.Core.Tests/Auth/MockOfAuthKeyManager.cs similarity index 98% rename from test/ProtonVPN.Core.Test/Auth/MockOfAuthKeyManager.cs rename to src/Tests/ProtonVPN.Core.Tests/Auth/MockOfAuthKeyManager.cs index 839e3df79..2c214b3e0 100644 --- a/test/ProtonVPN.Core.Test/Auth/MockOfAuthKeyManager.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Auth/MockOfAuthKeyManager.cs @@ -24,7 +24,7 @@ using ProtonVPN.Core.Auth; using ProtonVPN.Crypto; -namespace ProtonVPN.Core.Test.Auth +namespace ProtonVPN.Core.Tests.Auth { public class MockOfAuthKeyManager : IAuthKeyManager { diff --git a/test/ProtonVPN.Core.Test/Models/UserTest.cs b/src/Tests/ProtonVPN.Core.Tests/Models/UserTest.cs similarity index 88% rename from test/ProtonVPN.Core.Test/Models/UserTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Models/UserTest.cs index 32c7891ee..dc38a4d89 100644 --- a/test/ProtonVPN.Core.Test/Models/UserTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Models/UserTest.cs @@ -19,9 +19,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using CoreUser = ProtonVPN.Core.Models.User; +using ProtonVPN.Core.Models; -namespace ProtonVPN.Core.Test.Models +namespace ProtonVPN.Core.Tests.Models { [TestClass] public class UserTest @@ -35,7 +35,7 @@ public class UserTest public void GetAccountPlan_ShouldBe_MappedFormServices(int services, string expected) { // Arrange - CoreUser user = new() { Services = services }; + User user = new() { Services = services }; // Act string result = user.GetAccountPlan(); // Assert @@ -51,7 +51,7 @@ public void GetAccountPlan_ShouldBe_MappedFormServices(int services, string expe public void Paid_ShouldBe_MappedFromVpnPlan(string vpnPlan, bool expected) { // Arrange - CoreUser user = new() { VpnPlan = vpnPlan }; + User user = new() { VpnPlan = vpnPlan }; // Act bool result = user.Paid(); // Assert @@ -65,7 +65,7 @@ public void Paid_ShouldBe_MappedFromVpnPlan(string vpnPlan, bool expected) public void Empty_ShouldBeTrue_WhenUsernameIsNullOrEmpty(string username, bool expected) { // Arrange - CoreUser user = new() { Username = username }; + User user = new() { Username = username }; // Act bool result = user.Empty(); // Assert @@ -76,7 +76,7 @@ public void Empty_ShouldBeTrue_WhenUsernameIsNullOrEmpty(string username, bool e public void EmptyUser_ShouldBe_Empty() { // Act - CoreUser user = CoreUser.EmptyUser(); + User user = User.EmptyUser(); // Assert user.Empty().Should().BeTrue(); } diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientExtensionsTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientExtensionsTest.cs similarity index 98% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientExtensionsTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientExtensionsTest.cs index 9e4e154e8..ac9d974b3 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientExtensionsTest.cs @@ -26,7 +26,7 @@ using NSubstitute; using ProtonVPN.Core.OS.Net.Dns; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] [SuppressMessage("ReSharper", "InvokeAsExtensionMethod")] diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientTest.cs similarity index 99% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientTest.cs index 1cff7ffc4..3ad2c23a5 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientTest.cs @@ -29,7 +29,7 @@ using ProtonVPN.Common.OS.Net.NetworkInterface; using ProtonVPN.Core.OS.Net.Dns; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] [SuppressMessage("ReSharper", "CoVariantArrayConversion")] diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientsTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientsTest.cs similarity index 98% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientsTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientsTest.cs index 97a497659..b2ad7ec0a 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/DnsClientsTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/DnsClientsTest.cs @@ -17,13 +17,13 @@ * along with ProtonVPN. If not, see . */ +using System.Diagnostics.CodeAnalysis; +using System.Net; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Core.OS.Net.Dns; -using System.Diagnostics.CodeAnalysis; -using System.Net; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] [SuppressMessage("ReSharper", "CoVariantArrayConversion")] diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/FixedDnsClientTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/FixedDnsClientTest.cs similarity index 98% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/FixedDnsClientTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/FixedDnsClientTest.cs index d35b2cca9..4ab54dbee 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/FixedDnsClientTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/FixedDnsClientTest.cs @@ -17,12 +17,6 @@ * along with ProtonVPN. If not, see . */ -using DnsClient; -using DnsClient.Protocol; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NSubstitute; -using ProtonVPN.Core.OS.Net.Dns; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -30,9 +24,14 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using ProtonVPN.Common.Logging; +using DnsClient; +using DnsClient.Protocol; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Core.OS.Net.Dns; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] [SuppressMessage("ReSharper", "CoVariantArrayConversion")] diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/NullDnsClientTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/NullDnsClientTest.cs similarity index 97% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/NullDnsClientTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/NullDnsClientTest.cs index 492e0021f..842ec0d69 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/NullDnsClientTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/NullDnsClientTest.cs @@ -17,13 +17,13 @@ * along with ProtonVPN. If not, see . */ +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Core.OS.Net.Dns; -using System.Threading; -using System.Threading.Tasks; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] public class NullDnsClientTest diff --git a/test/ProtonVPN.Core.Test/OS/Net/Dns/SafeDnsClientTest.cs b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/SafeDnsClientTest.cs similarity index 99% rename from test/ProtonVPN.Core.Test/OS/Net/Dns/SafeDnsClientTest.cs rename to src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/SafeDnsClientTest.cs index fcc893efb..438faf296 100644 --- a/test/ProtonVPN.Core.Test/OS/Net/Dns/SafeDnsClientTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/OS/Net/Dns/SafeDnsClientTest.cs @@ -17,12 +17,6 @@ * along with ProtonVPN. If not, see . */ -using DnsClient; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using ProtonVPN.Core.OS.Net.Dns; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -30,8 +24,14 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using DnsClient; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using ProtonVPN.Core.OS.Net.Dns; -namespace ProtonVPN.Core.Test.OS.Net.Dns +namespace ProtonVPN.Core.Tests.OS.Net.Dns { [TestClass] [SuppressMessage("ReSharper", "CoVariantArrayConversion")] diff --git a/test/ProtonVPN.Core.Test/Profiles/ProfileExtensionsTest.cs b/src/Tests/ProtonVPN.Core.Tests/Profiles/ProfileExtensionsTest.cs similarity index 99% rename from test/ProtonVPN.Core.Test/Profiles/ProfileExtensionsTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Profiles/ProfileExtensionsTest.cs index 3b6734835..9f9bcff83 100644 --- a/test/ProtonVPN.Core.Test/Profiles/ProfileExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Profiles/ProfileExtensionsTest.cs @@ -25,7 +25,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Core.Profiles; -namespace ProtonVPN.Core.Test.Profiles +namespace ProtonVPN.Core.Tests.Profiles { [TestClass] public class ProfileExtensionsTest diff --git a/test/ProtonVPN.Core.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Core.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.Core.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Core.Tests/Properties/AssemblyInfo.cs index d81ec428b..8718c9fe0 100644 --- a/test/ProtonVPN.Core.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Properties/AssemblyInfo.cs @@ -21,11 +21,11 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Core.Test")] +[assembly: AssemblyTitle("ProtonVPN.Core.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Core.Test")] +[assembly: AssemblyProduct("ProtonVPN.Core.Tests")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.Core.Tests/ProtonVPN.Core.Tests.csproj b/src/Tests/ProtonVPN.Core.Tests/ProtonVPN.Core.Tests.csproj new file mode 100644 index 000000000..3ff6a2fc4 --- /dev/null +++ b/src/Tests/ProtonVPN.Core.Tests/ProtonVPN.Core.Tests.csproj @@ -0,0 +1,151 @@ + + + + Debug + AnyCPU + {FA0D86B4-2B86-4DFE-B7E6-7C809DB74A13} + Library + Properties + ProtonVPN.Core.Tests + ProtonVPN.Core.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.2.0 + + + 1.5.0 + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 13.0.1 + + + 4.3.0 + + + 7.2.0 + + + 4.3.4 + + + 4.3.1 + + + 5.0.0 + + + 4.3.0 + + + 4.5.4 + + + 4.5.0 + + + + + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} + ProtonVPN.Api.Contracts + + + {3E905528-D87C-4552-A32D-66BF90D14DB0} + ProtonVPN.Api + + + {0cdca012-bb2d-49b3-944e-ce80d75d651a} + ProtonVPN.App + + + {03b8e43c-5680-4803-a745-0a104fe6620c} + ProtonVPN.Common + + + {ca44b51d-7645-413a-818f-2c5b57db67dd} + ProtonVPN.Core + + + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} + ProtonVPN.Crypto + + + {A0DA4200-6643-4F2C-8450-65B8CE8A5576} + ProtonVPN.Tests.Common + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerLoadUpdaterTest.cs similarity index 99% rename from test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Servers/ServerLoadUpdaterTest.cs index 490228eda..590340a8a 100644 --- a/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerLoadUpdaterTest.cs @@ -29,7 +29,7 @@ using ProtonVPN.Core.Settings; using ProtonVPN.Core.Windows; -namespace ProtonVPN.Core.Test.Servers +namespace ProtonVPN.Core.Tests.Servers { [TestClass] public class ServerLoadUpdaterTest diff --git a/test/ProtonVPN.Core.Test/Servers/ServerManagerTest.cs b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerManagerTest.cs similarity index 96% rename from test/ProtonVPN.Core.Test/Servers/ServerManagerTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Servers/ServerManagerTest.cs index ef8170e8a..dd46a30c5 100644 --- a/test/ProtonVPN.Core.Test/Servers/ServerManagerTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerManagerTest.cs @@ -25,10 +25,11 @@ using ProtonVPN.Common.Logging; using ProtonVPN.Common.Networking; using ProtonVPN.Core.Abstract; +using ProtonVPN.Core.Models; using ProtonVPN.Core.Servers; using ProtonVPN.Core.Settings; -namespace ProtonVPN.Core.Test.Servers +namespace ProtonVPN.Core.Tests.Servers { [TestClass] public class ServerManagerTest @@ -58,7 +59,7 @@ public void ItShouldSkipServersWithoutPublicKeyForWireguard() { // Arrange _appSettings.GetProtocol().Returns(VpnProtocol.WireGuard); - _userStorage.User().Returns(new Core.Models.User {MaxTier = 0}); + _userStorage.GetUser().Returns(new User {MaxTier = 0}); ServerManager serverManager = new ServerManager(_userStorage, _appSettings, _logger); // Act diff --git a/test/ProtonVPN.Core.Test/Servers/ServerSpecsTest.cs b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerSpecsTest.cs similarity index 98% rename from test/ProtonVPN.Core.Test/Servers/ServerSpecsTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Servers/ServerSpecsTest.cs index d92f90638..fe73a56ba 100644 --- a/test/ProtonVPN.Core.Test/Servers/ServerSpecsTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerSpecsTest.cs @@ -23,7 +23,7 @@ using ProtonVPN.Core.Servers; using ProtonVPN.Core.Servers.Specs; -namespace ProtonVPN.Core.Test.Servers +namespace ProtonVPN.Core.Tests.Servers { [TestClass] public class EntryCountryServerTest diff --git a/test/ProtonVPN.Core.Test/Servers/ServerTest.cs b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerTest.cs similarity index 98% rename from test/ProtonVPN.Core.Test/Servers/ServerTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Servers/ServerTest.cs index 418fdd1f5..9a835f096 100644 --- a/test/ProtonVPN.Core.Test/Servers/ServerTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Servers/ServerTest.cs @@ -25,7 +25,7 @@ using PhysicalServer = ProtonVPN.Core.Servers.Models.PhysicalServer; using Server = ProtonVPN.Core.Servers.Models.Server; -namespace ProtonVPN.Core.Test.Servers +namespace ProtonVPN.Core.Tests.Servers { [TestClass] public class ServerTest diff --git a/test/ProtonVPN.Core.Test/User/TruncatedLocationTest.cs b/src/Tests/ProtonVPN.Core.Tests/Users/TruncatedLocationTest.cs similarity index 90% rename from test/ProtonVPN.Core.Test/User/TruncatedLocationTest.cs rename to src/Tests/ProtonVPN.Core.Tests/Users/TruncatedLocationTest.cs index c6f83ac1b..56957473d 100644 --- a/test/ProtonVPN.Core.Test/User/TruncatedLocationTest.cs +++ b/src/Tests/ProtonVPN.Core.Tests/Users/TruncatedLocationTest.cs @@ -21,9 +21,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Core.Settings; -using ProtonVPN.Core.User; +using ProtonVPN.Core.Users; -namespace ProtonVPN.Core.Test.User +namespace ProtonVPN.Core.Tests.Users { [TestClass] public class TruncatedLocationTest @@ -43,7 +43,7 @@ public void Value_ShouldBe_IpWithZeroedLastOctet(string ip) { // Arrange UserLocation userLocation = new(ip, "ISP", "ZZ"); - _userStorage.Location().Returns(userLocation); + _userStorage.GetLocation().Returns(userLocation); TruncatedLocation location = new(_userStorage); // Act @@ -58,7 +58,7 @@ public void Value_ShouldBeEmpty_WhenIpIsNull() { // Arrange UserLocation userLocation = new(null, "ISP", "ZZ"); - _userStorage.Location().Returns(userLocation); + _userStorage.GetLocation().Returns(userLocation); TruncatedLocation location = new(_userStorage); // Act @@ -73,7 +73,7 @@ public void Value_ShouldBeEmpty_WhenIpIsEmpty() { // Arrange UserLocation userLocation = new(string.Empty, "ISP", "ZZ"); - _userStorage.Location().Returns(userLocation); + _userStorage.GetLocation().Returns(userLocation); TruncatedLocation location = new(_userStorage); // Act diff --git a/test/ProtonVPN.Core.Test/app.config b/src/Tests/ProtonVPN.Core.Tests/app.config similarity index 72% rename from test/ProtonVPN.Core.Test/app.config rename to src/Tests/ProtonVPN.Core.Tests/app.config index 4e025ec35..ba0ac79da 100644 --- a/test/ProtonVPN.Core.Test/app.config +++ b/src/Tests/ProtonVPN.Core.Tests/app.config @@ -6,10 +6,6 @@ - - - - diff --git a/test/ProtonVPN.Crypto.Test/Ed25519Asn1KeyGeneratorTest.cs b/src/Tests/ProtonVPN.Crypto.Tests/Ed25519Asn1KeyGeneratorTest.cs similarity index 98% rename from test/ProtonVPN.Crypto.Test/Ed25519Asn1KeyGeneratorTest.cs rename to src/Tests/ProtonVPN.Crypto.Tests/Ed25519Asn1KeyGeneratorTest.cs index eec0aa079..3111f352e 100644 --- a/test/ProtonVPN.Crypto.Test/Ed25519Asn1KeyGeneratorTest.cs +++ b/src/Tests/ProtonVPN.Crypto.Tests/Ed25519Asn1KeyGeneratorTest.cs @@ -22,7 +22,7 @@ you can redistribute it and/or modify using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace ProtonVPN.Crypto.Test +namespace ProtonVPN.Crypto.Tests { [TestClass] public class Ed25519Asn1KeyGeneratorTest diff --git a/test/ProtonVPN.Crypto.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Crypto.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.Crypto.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Crypto.Tests/Properties/AssemblyInfo.cs index 3e3138f69..03030ac48 100644 --- a/test/ProtonVPN.Crypto.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Crypto.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Crypto.Test")] +[assembly: AssemblyTitle("ProtonVPN.Crypto.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Crypto.Test")] +[assembly: AssemblyProduct("ProtonVPN.Crypto.Tests")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/test/ProtonVPN.Crypto.Test/ProtonVPN.Crypto.Test.csproj b/src/Tests/ProtonVPN.Crypto.Tests/ProtonVPN.Crypto.Tests.csproj similarity index 55% rename from test/ProtonVPN.Crypto.Test/ProtonVPN.Crypto.Test.csproj rename to src/Tests/ProtonVPN.Crypto.Tests/ProtonVPN.Crypto.Tests.csproj index 69c72e278..9484b2d3e 100644 --- a/test/ProtonVPN.Crypto.Test/ProtonVPN.Crypto.Test.csproj +++ b/src/Tests/ProtonVPN.Crypto.Tests/ProtonVPN.Crypto.Tests.csproj @@ -1,6 +1,5 @@  - Debug @@ -8,8 +7,8 @@ {7D608265-3330-4747-B5B4-9673A119FE6C} Library Properties - ProtonVPN.Crypto.Test - ProtonVPN.Crypto.Test + ProtonVPN.Crypto.Tests + ProtonVPN.Crypto.Tests v4.7.2 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -25,7 +24,7 @@ true full false - bin\Debug\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -33,18 +32,12 @@ pdbonly true - bin\Release\ + ..\..\bin\ TRACE prompt 4 - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - @@ -55,22 +48,19 @@ - + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} ProtonVPN.Crypto - + + 2.1.2 + + + 2.1.2 + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Crypto.Test/X25519KeyGeneratorTest.cs b/src/Tests/ProtonVPN.Crypto.Tests/X25519KeyGeneratorTest.cs similarity index 98% rename from test/ProtonVPN.Crypto.Test/X25519KeyGeneratorTest.cs rename to src/Tests/ProtonVPN.Crypto.Tests/X25519KeyGeneratorTest.cs index ea2e7e452..b0f4a737e 100644 --- a/test/ProtonVPN.Crypto.Test/X25519KeyGeneratorTest.cs +++ b/src/Tests/ProtonVPN.Crypto.Tests/X25519KeyGeneratorTest.cs @@ -19,7 +19,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace ProtonVPN.Crypto.Test +namespace ProtonVPN.Crypto.Tests { [TestClass] public class X25519KeyGeneratorTest diff --git a/test/ProtonVPN.IntegrationTests/Announcements/AnnouncementCacheMock.cs b/src/Tests/ProtonVPN.IntegrationTests/Announcements/AnnouncementCacheMock.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/Announcements/AnnouncementCacheMock.cs rename to src/Tests/ProtonVPN.IntegrationTests/Announcements/AnnouncementCacheMock.cs diff --git a/test/ProtonVPN.IntegrationTests/Announcements/AnnouncementServiceTest.cs b/src/Tests/ProtonVPN.IntegrationTests/Announcements/AnnouncementServiceTest.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/Announcements/AnnouncementServiceTest.cs rename to src/Tests/ProtonVPN.IntegrationTests/Announcements/AnnouncementServiceTest.cs diff --git a/test/ProtonVPN.IntegrationTests/Auth/AuthCertificateTests.cs b/src/Tests/ProtonVPN.IntegrationTests/Auth/AuthCertificateTests.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/Auth/AuthCertificateTests.cs rename to src/Tests/ProtonVPN.IntegrationTests/Auth/AuthCertificateTests.cs diff --git a/test/ProtonVPN.IntegrationTests/Auth/LoginTests.cs b/src/Tests/ProtonVPN.IntegrationTests/Auth/LoginTests.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/Auth/LoginTests.cs rename to src/Tests/ProtonVPN.IntegrationTests/Auth/LoginTests.cs diff --git a/test/ProtonVPN.IntegrationTests/AuthenticatedUserTests.cs b/src/Tests/ProtonVPN.IntegrationTests/AuthenticatedUserTests.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/AuthenticatedUserTests.cs rename to src/Tests/ProtonVPN.IntegrationTests/AuthenticatedUserTests.cs diff --git a/test/ProtonVPN.IntegrationTests/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.IntegrationTests/Properties/AssemblyInfo.cs similarity index 100% rename from test/ProtonVPN.IntegrationTests/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.IntegrationTests/Properties/AssemblyInfo.cs diff --git a/test/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj b/src/Tests/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj similarity index 88% rename from test/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj rename to src/Tests/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj index cc3f71365..976378e29 100644 --- a/test/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj +++ b/src/Tests/ProtonVPN.IntegrationTests/ProtonVPN.IntegrationTests.csproj @@ -24,7 +24,7 @@ true full false - ..\..\src\bin\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -33,7 +33,7 @@ pdbonly true - ..\..\src\bin\ + ..\..\bin\ TRACE prompt 4 @@ -63,35 +63,35 @@ - + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} ProtonVPN.Api.Contracts - + {686E902E-0C23-4396-8887-6D9219EF8D27} ProtonVPN.Api.Installers - + {3E905528-D87C-4552-A32D-66BF90D14DB0} ProtonVPN.Api - + {0CDCA012-BB2D-49B3-944E-CE80D75D651A} ProtonVPN.App - + {03B8E43C-5680-4803-A745-0A104FE6620C} ProtonVPN.Common - + {CA44B51D-7645-413A-818F-2C5B57DB67DD} ProtonVPN.Core - + {80A71107-9C8E-47B2-B743-58D5976B38FB} ProtonVPN.HumanVerification.Installers - + {90fdf2b3-25c9-428d-b264-5a5faeb2d988} ProtonVPN.Update diff --git a/test/ProtonVPN.IntegrationTests/TestBase.cs b/src/Tests/ProtonVPN.IntegrationTests/TestBase.cs similarity index 95% rename from test/ProtonVPN.IntegrationTests/TestBase.cs rename to src/Tests/ProtonVPN.IntegrationTests/TestBase.cs index 828573932..453cdbc59 100644 --- a/test/ProtonVPN.IntegrationTests/TestBase.cs +++ b/src/Tests/ProtonVPN.IntegrationTests/TestBase.cs @@ -78,12 +78,12 @@ protected void InitializeContainer() builder.Register(_ => { HttpClient httpClient = MessageHandler.ToHttpClient(); - IHttpClientFactory httpClientFactory = Substitute.For(); + IApiHttpClientFactory httpClientFactory = Substitute.For(); httpClient.BaseAddress = new Uri("http://localhost"); httpClientFactory.GetApiHttpClientWithCache().Returns(httpClient); httpClientFactory.GetApiHttpClientWithoutCache().Returns(httpClient); return httpClientFactory; - }).As().SingleInstance(); + }).As().SingleInstance(); new Update.Config.Module().Load(builder); diff --git a/test/ProtonVPN.IntegrationTests/TestData/AnnouncementsResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/AnnouncementsResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/AnnouncementsResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/AnnouncementsResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/AuthInfoResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/AuthInfoResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/AuthInfoResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/AuthInfoResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/AuthResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/AuthResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/AuthResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/AuthResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/AuthResponseWithTwoFactorEnabledMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/AuthResponseWithTwoFactorEnabledMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/AuthResponseWithTwoFactorEnabledMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/AuthResponseWithTwoFactorEnabledMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/CertificateResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/CertificateResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/CertificateResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/CertificateResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/InvalidDataAnnouncementsResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/InvalidDataAnnouncementsResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/InvalidDataAnnouncementsResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/InvalidDataAnnouncementsResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/MissingOfferAnnouncementsResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/MissingOfferAnnouncementsResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/MissingOfferAnnouncementsResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/MissingOfferAnnouncementsResponseMock.json diff --git a/test/ProtonVPN.IntegrationTests/TestData/VpnInfoWrapperResponseMock.json b/src/Tests/ProtonVPN.IntegrationTests/TestData/VpnInfoWrapperResponseMock.json similarity index 100% rename from test/ProtonVPN.IntegrationTests/TestData/VpnInfoWrapperResponseMock.json rename to src/Tests/ProtonVPN.IntegrationTests/TestData/VpnInfoWrapperResponseMock.json diff --git a/test/ProtonVPN.Service.Contract.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Service.Contract.Tests/Properties/AssemblyInfo.cs similarity index 90% rename from test/ProtonVPN.Service.Contract.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Service.Contract.Tests/Properties/AssemblyInfo.cs index ea8641540..2a6dcef78 100644 --- a/test/ProtonVPN.Service.Contract.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Service.Contract.Test")] +[assembly: AssemblyTitle("ProtonVPN.Service.Contract.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Service.Contract.Test")] +[assembly: AssemblyProduct("ProtonVPN.Service.Contract.Tests")] [assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.Service.Contract.Tests/ProtonVPN.Service.Contract.Tests.csproj b/src/Tests/ProtonVPN.Service.Contract.Tests/ProtonVPN.Service.Contract.Tests.csproj new file mode 100644 index 000000000..8d355810e --- /dev/null +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/ProtonVPN.Service.Contract.Tests.csproj @@ -0,0 +1,101 @@ + + + + + Debug + AnyCPU + {C4E97B08-345A-423B-BD4C-76593D1401B6} + Library + Properties + ProtonVPN.Service.Contract.Tests + ProtonVPN.Service.Contract.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} + ProtonVPN.Crypto + + + {96c5d688-c0f1-4a63-9e26-e485fd0e1365} + ProtonVPN.Service.Contract + + + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 4.3.4 + + + 4.3.1 + + + 5.0.0 + + + 4.5.4 + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnConnectionRequestContractTest.cs b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnConnectionRequestContractTest.cs similarity index 98% rename from test/ProtonVPN.Service.Contract.Test/Vpn/VpnConnectionRequestContractTest.cs rename to src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnConnectionRequestContractTest.cs index 2af170694..5e6eefc63 100644 --- a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnConnectionRequestContractTest.cs +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnConnectionRequestContractTest.cs @@ -25,7 +25,7 @@ using ProtonVPN.Service.Contract.Crypto; using ProtonVPN.Service.Contract.Vpn; -namespace ProtonVPN.Service.Contract.Test.Vpn +namespace ProtonVPN.Service.Contract.Tests.Vpn { [TestClass] public class VpnConnectionRequestContractTest diff --git a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnErrorTypeContractTest.cs b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnErrorTypeContractTest.cs similarity index 97% rename from test/ProtonVPN.Service.Contract.Test/Vpn/VpnErrorTypeContractTest.cs rename to src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnErrorTypeContractTest.cs index cb75a258e..92490f3e3 100644 --- a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnErrorTypeContractTest.cs +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnErrorTypeContractTest.cs @@ -17,14 +17,14 @@ * along with ProtonVPN. If not, see . */ +using System; +using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Vpn; using ProtonVPN.Service.Contract.Vpn; -using System; -using System.Linq; -namespace ProtonVPN.Service.Contract.Test.Vpn +namespace ProtonVPN.Service.Contract.Tests.Vpn { [TestClass] public class VpnErrorTypeContractTest diff --git a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnProtocolContractTest.cs b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnProtocolContractTest.cs similarity index 97% rename from test/ProtonVPN.Service.Contract.Test/Vpn/VpnProtocolContractTest.cs rename to src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnProtocolContractTest.cs index 24854ec42..f3481544e 100644 --- a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnProtocolContractTest.cs +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnProtocolContractTest.cs @@ -24,7 +24,7 @@ using ProtonVPN.Common.Networking; using ProtonVPN.Service.Contract.Vpn; -namespace ProtonVPN.Service.Contract.Test.Vpn +namespace ProtonVPN.Service.Contract.Tests.Vpn { [TestClass] public class VpnProtocolContractTest diff --git a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnStatusContractTest.cs b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnStatusContractTest.cs similarity index 97% rename from test/ProtonVPN.Service.Contract.Test/Vpn/VpnStatusContractTest.cs rename to src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnStatusContractTest.cs index 62f720bc6..aa9583e93 100644 --- a/test/ProtonVPN.Service.Contract.Test/Vpn/VpnStatusContractTest.cs +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/Vpn/VpnStatusContractTest.cs @@ -17,14 +17,14 @@ * along with ProtonVPN. If not, see . */ +using System; +using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Vpn; using ProtonVPN.Service.Contract.Vpn; -using System; -using System.Linq; -namespace ProtonVPN.Service.Contract.Test.Vpn +namespace ProtonVPN.Service.Contract.Tests.Vpn { [TestClass] public class VpnStatusContractTest diff --git a/src/Tests/ProtonVPN.Service.Contract.Tests/app.config b/src/Tests/ProtonVPN.Service.Contract.Tests/app.config new file mode 100644 index 000000000..f10b4ac05 --- /dev/null +++ b/src/Tests/ProtonVPN.Service.Contract.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Service.Test/Config/ConfigDirectoriesTest.cs b/src/Tests/ProtonVPN.Service.Tests/Config/ConfigDirectoriesTest.cs similarity index 98% rename from test/ProtonVPN.Service.Test/Config/ConfigDirectoriesTest.cs rename to src/Tests/ProtonVPN.Service.Tests/Config/ConfigDirectoriesTest.cs index dda13b3cd..c23a8b528 100644 --- a/test/ProtonVPN.Service.Test/Config/ConfigDirectoriesTest.cs +++ b/src/Tests/ProtonVPN.Service.Tests/Config/ConfigDirectoriesTest.cs @@ -22,9 +22,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Service.Config; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; -namespace ProtonVPN.Service.Test.Config +namespace ProtonVPN.Service.Tests.Config { [TestClass] public class ConfigDirectoriesTest diff --git a/test/ProtonVPN.Service.Test/KillSwitch/KillSwitchTest.cs b/src/Tests/ProtonVPN.Service.Tests/KillSwitch/KillSwitchTest.cs similarity index 99% rename from test/ProtonVPN.Service.Test/KillSwitch/KillSwitchTest.cs rename to src/Tests/ProtonVPN.Service.Tests/KillSwitch/KillSwitchTest.cs index 44ff904f0..32e56e2ed 100644 --- a/test/ProtonVPN.Service.Test/KillSwitch/KillSwitchTest.cs +++ b/src/Tests/ProtonVPN.Service.Tests/KillSwitch/KillSwitchTest.cs @@ -29,7 +29,7 @@ using ProtonVPN.Service.Settings; using ProtonVPN.Vpn.Common; -namespace ProtonVPN.Service.Test.KillSwitch +namespace ProtonVPN.Service.Tests.KillSwitch { [TestClass] public class KillSwitchTest diff --git a/test/ProtonVPN.Service.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Service.Tests/Properties/AssemblyInfo.cs similarity index 91% rename from test/ProtonVPN.Service.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Service.Tests/Properties/AssemblyInfo.cs index b98feca92..99f6bac48 100644 --- a/test/ProtonVPN.Service.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Service.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Service.Test")] +[assembly: AssemblyTitle("ProtonVPN.Service.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Service.Test")] +[assembly: AssemblyProduct("ProtonVPN.Service.Tests")] [assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.Service.Tests/ProtonVPN.Service.Tests.csproj b/src/Tests/ProtonVPN.Service.Tests/ProtonVPN.Service.Tests.csproj new file mode 100644 index 000000000..060575305 --- /dev/null +++ b/src/Tests/ProtonVPN.Service.Tests/ProtonVPN.Service.Tests.csproj @@ -0,0 +1,117 @@ + + + + + Debug + AnyCPU + {4290C007-2142-4AD1-8EB6-F80EF2F45AA4} + Library + Properties + ProtonVPN.Service.Tests + ProtonVPN.Service.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {1cf1b8bf-57eb-4e49-b644-0a8f2dfeeb58} + ProtonVPN.NetworkFilter + + + {96C5D688-C0F1-4A63-9E26-E485FD0E1365} + ProtonVPN.Service.Contract + + + {25781B52-5858-4387-80A5-C9C38C32B3CC} + ProtonVPN.Service + + + {4aa7ce6f-7154-49c1-b598-46055d590cad} + ProtonVPN.Vpn + + + {A0DA4200-6643-4F2C-8450-65B8CE8A5576} + ProtonVPN.Tests.Common + + + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 4.3.0 + + + 4.3.4 + + + 4.3.1 + + + 5.0.0 + + + 4.5.4 + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Service.Test/SplitTunnel/ReverseSplitTunnelAppsTest.cs b/src/Tests/ProtonVPN.Service.Tests/SplitTunneling/ReverseSplitTunnelAppsTest.cs similarity index 97% rename from test/ProtonVPN.Service.Test/SplitTunnel/ReverseSplitTunnelAppsTest.cs rename to src/Tests/ProtonVPN.Service.Tests/SplitTunneling/ReverseSplitTunnelAppsTest.cs index 0e7a9b076..cd8ea2539 100644 --- a/test/ProtonVPN.Service.Test/SplitTunnel/ReverseSplitTunnelAppsTest.cs +++ b/src/Tests/ProtonVPN.Service.Tests/SplitTunneling/ReverseSplitTunnelAppsTest.cs @@ -24,7 +24,7 @@ using ProtonVPN.Service.Settings; using ProtonVPN.Service.SplitTunneling; -namespace ProtonVPN.Service.Test.SplitTunnel +namespace ProtonVPN.Service.Tests.SplitTunneling { [TestClass] public class ReverseSplitTunnelAppsTest diff --git a/test/ProtonVPN.Service.Test/SplitTunnel/SplitTunnelTest.cs b/src/Tests/ProtonVPN.Service.Tests/SplitTunneling/SplitTunnelTest.cs similarity index 88% rename from test/ProtonVPN.Service.Test/SplitTunnel/SplitTunnelTest.cs rename to src/Tests/ProtonVPN.Service.Tests/SplitTunneling/SplitTunnelTest.cs index 181dac298..572c1ee0a 100644 --- a/test/ProtonVPN.Service.Test/SplitTunnel/SplitTunnelTest.cs +++ b/src/Tests/ProtonVPN.Service.Tests/SplitTunneling/SplitTunnelTest.cs @@ -28,7 +28,7 @@ using ProtonVPN.Service.SplitTunneling; using ProtonVPN.Vpn.Common; -namespace ProtonVPN.Service.Test.SplitTunnel +namespace ProtonVPN.Service.Tests.SplitTunneling { [TestClass] public class SplitTunnelTest @@ -57,7 +57,7 @@ public void OnVpnConnecting_WhenBlockMode_DisableReversed() { Mode = SplitTunnelMode.Block }); - var splitTunnel = GetSplitTunnel(false, true); + SplitTunnel splitTunnel = GetSplitTunnel(false, true); // Act splitTunnel.OnVpnConnecting(GetConnectingVpnState()); @@ -74,7 +74,7 @@ public void OnVpnConnecting_WhenBlockMode_Disable() { Mode = SplitTunnelMode.Permit }); - var splitTunnel = GetSplitTunnel(true); + SplitTunnel splitTunnel = GetSplitTunnel(true); // Act splitTunnel.OnVpnConnecting(GetConnectingVpnState()); @@ -87,14 +87,14 @@ public void OnVpnConnecting_WhenBlockMode_Disable() public void OnVpnConnected_PermitRemoteAddressesOnBlockMode() { // Arrange - var addresses = new[] { "127.0.0.1", "192.168.0.1", "8.8.8.8" }; + string[] addresses = new[] { "127.0.0.1", "192.168.0.1", "8.8.8.8" }; _serviceSettings.SplitTunnelSettings.Returns(new SplitTunnelSettingsContract { Mode = SplitTunnelMode.Block, Ips = addresses, AppPaths = new string[] {}, }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -113,7 +113,7 @@ public void OnVpnConnected_WhenBlockMode_CallEnable() AppPaths = new string[] {}, Ips = new string[] {}, }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -132,7 +132,7 @@ public void OnVpnConnected_WhenBlockMode_CalloutDriverStart() AppPaths = new string[] {}, Ips = new string[] { }, }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -146,7 +146,7 @@ public void OnVpnConnected_WhenPermitMode_CalloutDriverStart() { Mode = SplitTunnelMode.Permit }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -160,7 +160,7 @@ public void OnVpnConnected_WhenDisabled_CalloutDriverDoNotStart() { Mode = SplitTunnelMode.Disabled }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -174,7 +174,7 @@ public void OnVpnConnected_WhenDisabled_DoNotEnable() { Mode = SplitTunnelMode.Disabled }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -191,7 +191,7 @@ public void OnVpnConnected_WhenPermitMode_EnableReversed() { Mode = SplitTunnelMode.Permit }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -206,14 +206,14 @@ public void OnVpnConnected_WhenPermitMode_EnableReversed() public void OnVpnConnected_PermitAppsOnBlockMode() { // Arrange - var apps = new[] { "app1", "app2", "app3" }; + string[] apps = new[] { "app1", "app2", "app3" }; _serviceSettings.SplitTunnelSettings.Returns(new SplitTunnelSettingsContract { Mode = SplitTunnelMode.Block, AppPaths = apps, Ips = new string[] {}, }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnConnected(GetConnectedVpnState()); @@ -226,13 +226,13 @@ public void OnVpnConnected_PermitAppsOnBlockMode() public void OnVpnConnecting_ShouldBlockApps_WhenModeIsPermit() { // Arrange - var apps = new [] {"app1", "app2", "app3"}; + string[] apps = new [] {"app1", "app2", "app3"}; _serviceSettings.SplitTunnelSettings.Returns(new SplitTunnelSettingsContract { Mode = SplitTunnelMode.Permit, AppPaths = apps }); - var splitTunnel = GetSplitTunnel(true); + SplitTunnel splitTunnel = GetSplitTunnel(true); // Act splitTunnel.OnVpnConnecting(GetConnectingVpnState()); @@ -249,7 +249,7 @@ public void OnVpnDisconnected_ManualDisconnect_ShouldDisable() { Mode = SplitTunnelMode.Block }); - var splitTunnel = GetSplitTunnel(true); + SplitTunnel splitTunnel = GetSplitTunnel(true); // Act splitTunnel.OnVpnDisconnected(GetDisconnectedVpnState(true)); @@ -266,7 +266,7 @@ public void OnVpnDisconnected_ManualDisconnect_ShouldDisableReversed() { Mode = SplitTunnelMode.Permit }); - var splitTunnel = GetSplitTunnel(false, true); + SplitTunnel splitTunnel = GetSplitTunnel(false, true); // Act splitTunnel.OnVpnDisconnected(GetDisconnectedVpnState(true)); @@ -283,15 +283,15 @@ public void OnVpnDisconnected_ManualDisconnect_ShouldStopCalloutDriver() { Mode = SplitTunnelMode.Permit }); - var splitTunnel = GetSplitTunnel(); + SplitTunnel splitTunnel = GetSplitTunnel(); // Act splitTunnel.OnVpnDisconnected(GetDisconnectedVpnState(true)); } - private SplitTunneling.SplitTunnel GetSplitTunnel(bool enabled = false, bool reverseEnabled = false) + private SplitTunnel GetSplitTunnel(bool enabled = false, bool reverseEnabled = false) { - return new SplitTunneling.SplitTunnel( + return new SplitTunnel( enabled, reverseEnabled, _serviceSettings, diff --git a/test/ProtonVPN.Service.Test/Vpn/ObservableNetworkInterfacesTest.cs b/src/Tests/ProtonVPN.Service.Tests/Vpn/ObservableNetworkInterfacesTest.cs similarity index 99% rename from test/ProtonVPN.Service.Test/Vpn/ObservableNetworkInterfacesTest.cs rename to src/Tests/ProtonVPN.Service.Tests/Vpn/ObservableNetworkInterfacesTest.cs index c115998ed..710d1c73f 100644 --- a/test/ProtonVPN.Service.Test/Vpn/ObservableNetworkInterfacesTest.cs +++ b/src/Tests/ProtonVPN.Service.Tests/Vpn/ObservableNetworkInterfacesTest.cs @@ -24,7 +24,7 @@ using ProtonVPN.Common.OS.Net.NetworkInterface; using ProtonVPN.Service.Vpn; -namespace ProtonVPN.Service.Test.Vpn +namespace ProtonVPN.Service.Tests.Vpn { [TestClass] public class ObservableNetworkInterfacesTest diff --git a/src/Tests/ProtonVPN.Service.Tests/app.config b/src/Tests/ProtonVPN.Service.Tests/app.config new file mode 100644 index 000000000..f10b4ac05 --- /dev/null +++ b/src/Tests/ProtonVPN.Service.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Test.Common/Breakpoints/Breakpoint.cs b/src/Tests/ProtonVPN.Tests.Common/Breakpoints/Breakpoint.cs similarity index 97% rename from test/ProtonVPN.Test.Common/Breakpoints/Breakpoint.cs rename to src/Tests/ProtonVPN.Tests.Common/Breakpoints/Breakpoint.cs index e07419dcb..263670133 100644 --- a/test/ProtonVPN.Test.Common/Breakpoints/Breakpoint.cs +++ b/src/Tests/ProtonVPN.Tests.Common/Breakpoints/Breakpoint.cs @@ -22,7 +22,7 @@ using System.Threading; using System.Threading.Tasks; -namespace ProtonVPN.Test.Common.Breakpoints +namespace ProtonVPN.Tests.Common.Breakpoints { public class Breakpoint : IDisposable { diff --git a/test/ProtonVPN.Test.Common/Breakpoints/BreakpointHit.cs b/src/Tests/ProtonVPN.Tests.Common/Breakpoints/BreakpointHit.cs similarity index 96% rename from test/ProtonVPN.Test.Common/Breakpoints/BreakpointHit.cs rename to src/Tests/ProtonVPN.Tests.Common/Breakpoints/BreakpointHit.cs index c4b0c220d..be1f4dfb2 100644 --- a/test/ProtonVPN.Test.Common/Breakpoints/BreakpointHit.cs +++ b/src/Tests/ProtonVPN.Tests.Common/Breakpoints/BreakpointHit.cs @@ -21,7 +21,7 @@ using System.Threading; using System.Threading.Tasks; -namespace ProtonVPN.Test.Common.Breakpoints +namespace ProtonVPN.Tests.Common.Breakpoints { public class BreakpointHit : IDisposable { diff --git a/test/ProtonVPN.Test.Common/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Tests.Common/Properties/AssemblyInfo.cs similarity index 94% rename from test/ProtonVPN.Test.Common/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Tests.Common/Properties/AssemblyInfo.cs index 2a64589e5..a9f3bf85b 100644 --- a/test/ProtonVPN.Test.Common/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Tests.Common/Properties/AssemblyInfo.cs @@ -23,11 +23,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("ProtonVPN.Test.Common")] +[assembly: AssemblyTitle("ProtonVPN.Tests.Common")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Test.Common")] +[assembly: AssemblyProduct("ProtonVPN.Tests.Common")] [assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/test/ProtonVPN.Test.Common/ProtonVPN.Test.Common.csproj b/src/Tests/ProtonVPN.Tests.Common/ProtonVPN.Tests.Common.csproj similarity index 91% rename from test/ProtonVPN.Test.Common/ProtonVPN.Test.Common.csproj rename to src/Tests/ProtonVPN.Tests.Common/ProtonVPN.Tests.Common.csproj index 8888784ba..ed9410c1f 100644 --- a/test/ProtonVPN.Test.Common/ProtonVPN.Test.Common.csproj +++ b/src/Tests/ProtonVPN.Tests.Common/ProtonVPN.Tests.Common.csproj @@ -7,8 +7,8 @@ {A0DA4200-6643-4F2C-8450-65B8CE8A5576} Library Properties - ProtonVPN.Test.Common - ProtonVPN.Test.Common + ProtonVPN.Tests.Common + ProtonVPN.Tests.Common v4.7.2 512 true @@ -19,7 +19,7 @@ true full false - ..\..\src\bin\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -27,7 +27,7 @@ pdbonly true - ..\..\src\bin\ + ..\..\bin\ TRACE prompt 4 diff --git a/test/ProtonVPN.Test.Common/TestConfig.cs b/src/Tests/ProtonVPN.Tests.Common/TestConfig.cs similarity index 97% rename from test/ProtonVPN.Test.Common/TestConfig.cs rename to src/Tests/ProtonVPN.Tests.Common/TestConfig.cs index 0acc4a022..eb93bb63e 100644 --- a/test/ProtonVPN.Test.Common/TestConfig.cs +++ b/src/Tests/ProtonVPN.Tests.Common/TestConfig.cs @@ -20,7 +20,7 @@ using System.IO; using System.Runtime.CompilerServices; -namespace ProtonVPN.Test.Common +namespace ProtonVPN.Tests.Common { public class TestConfig { diff --git a/test/ProtonVPN.UI.Test/ApiClient/TestRailApiClient.cs b/src/Tests/ProtonVPN.UI.Tests/ApiClient/TestRailApiClient.cs similarity index 94% rename from test/ProtonVPN.UI.Test/ApiClient/TestRailApiClient.cs rename to src/Tests/ProtonVPN.UI.Tests/ApiClient/TestRailApiClient.cs index 18da45688..1545f55d7 100644 --- a/test/ProtonVPN.UI.Test/ApiClient/TestRailApiClient.cs +++ b/src/Tests/ProtonVPN.UI.Tests/ApiClient/TestRailApiClient.cs @@ -19,17 +19,15 @@ using System; using System.Collections.Generic; -using System.IO; -using FlaUI.Core.Capturing; using NUnit.Framework; using NUnit.Framework.Interfaces; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; using TestRail; using TestRail.Enums; using TestRail.Types; using TestRail.Utils; -namespace ProtonVPN.UI.Test.ApiClient +namespace ProtonVPN.UI.Tests.ApiClient { public class TestRailApiClient : TestSession { @@ -52,7 +50,7 @@ public void CreateTestRun(string testRunName) public void MarkTestsByStatus() { - if(TestEnvironment.AreTestsRunningLocally()) + if(TestEnvironment.AreTestsRunningLocally() || TestEnvironment.IsWindows11()) { return; } diff --git a/test/ProtonVPN.UI.Test/ApiClient/TestsApiClient.cs b/src/Tests/ProtonVPN.UI.Tests/ApiClient/TestsApiClient.cs similarity index 97% rename from test/ProtonVPN.UI.Test/ApiClient/TestsApiClient.cs rename to src/Tests/ProtonVPN.UI.Tests/ApiClient/TestsApiClient.cs index 43739d269..9b94801b9 100644 --- a/test/ProtonVPN.UI.Test/ApiClient/TestsApiClient.cs +++ b/src/Tests/ProtonVPN.UI.Tests/ApiClient/TestsApiClient.cs @@ -22,7 +22,7 @@ using System.Threading.Tasks; using Newtonsoft.Json.Linq; -namespace ProtonVPN.UI.Test.ApiClient +namespace ProtonVPN.UI.Tests.ApiClient { public class TestsApiClient { diff --git a/test/ProtonVPN.UI.Test/DesktopActions.cs b/src/Tests/ProtonVPN.UI.Tests/DesktopActions.cs similarity index 95% rename from test/ProtonVPN.UI.Test/DesktopActions.cs rename to src/Tests/ProtonVPN.UI.Tests/DesktopActions.cs index fcdb2e3be..5d89c63e9 100644 --- a/test/ProtonVPN.UI.Test/DesktopActions.cs +++ b/src/Tests/ProtonVPN.UI.Tests/DesktopActions.cs @@ -22,9 +22,9 @@ using FlaUI.Core.Tools; using FlaUI.UIA3; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test +namespace ProtonVPN.UI.Tests { internal class DesktopActions { @@ -120,19 +120,19 @@ protected dynamic WaitUntilDisplayedByClass(string className, TimeSpan time) protected AutomationElement ElementByAutomationId(string automationId) { - WaitUntilExistsByAutomationId(automationId, TestConstants.TrayElementTimeout); + WaitUntilExistsByAutomationId(automationId, TestConstants.ShortTimeout); return Desktop.FindFirstDescendant(cf => cf.ByAutomationId(automationId)); } protected AutomationElement ElementByClassName(string className) { - WaitUntilDisplayedByClass(className, TestConstants.TrayElementTimeout); + WaitUntilDisplayedByClass(className, TestConstants.ShortTimeout); return Desktop.FindFirstDescendant(cf => cf.ByClassName(className)); } protected AutomationElement ElementByName(string name) { - WaitUntilExistsByName(name, TestConstants.TrayElementTimeout); + WaitUntilExistsByName(name, TestConstants.ShortTimeout); return Desktop.FindFirstDescendant(cf => cf.ByName(name)); } diff --git a/test/ProtonVPN.UI.Test/InstallerScripts/FreshInstall.xml b/src/Tests/ProtonVPN.UI.Tests/InstallerScripts/FreshInstall.xml similarity index 100% rename from test/ProtonVPN.UI.Test/InstallerScripts/FreshInstall.xml rename to src/Tests/ProtonVPN.UI.Tests/InstallerScripts/FreshInstall.xml diff --git a/test/ProtonVPN.UI.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.UI.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.UI.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.UI.Tests/Properties/AssemblyInfo.cs index 5c2f161ed..135931623 100644 --- a/test/ProtonVPN.UI.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Properties/AssemblyInfo.cs @@ -21,11 +21,11 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.UI.Test")] +[assembly: AssemblyTitle("ProtonVPN.UI.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.UI.Test")] +[assembly: AssemblyProduct("ProtonVPN.UI.Tests")] [assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.UI.Tests/ProtonVPN.UI.Tests.csproj b/src/Tests/ProtonVPN.UI.Tests/ProtonVPN.UI.Tests.csproj new file mode 100644 index 000000000..b6acd79d5 --- /dev/null +++ b/src/Tests/ProtonVPN.UI.Tests/ProtonVPN.UI.Tests.csproj @@ -0,0 +1,176 @@ + + + + + Debug + AnyCPU + {24E940FF-C9F3-4D5C-8FCF-CA527F055318} + Library + Properties + ProtonVPN.UI.Tests + ProtonVPN.UI.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + x64 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {0CDCA012-BB2D-49B3-944E-CE80D75D651A} + ProtonVPN.App + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {CA44B51D-7645-413A-818F-2C5B57DB67DD} + ProtonVPN.Core + + + {96C5D688-C0F1-4A63-9E26-E485FD0E1365} + ProtonVPN.Service.Contract + + + {F059E362-20A2-472B-82CA-E727D31AC0C7} + TestTools.ApiClient + + + + + 4.1.1 + + + 4.9.4 + + + 3.11.0 + + + 3.2.0 + + + 2.1.2 + + + 2.1.2 + + + 13.0.1 + + + 4.3.0 + + + 3.12.0 + + + 3.16.1 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 1.2.2 + + + 5.0.0 + + + 4.5.4 + + + 3.2.1 + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.UI.Test/Results/HomeResult.cs b/src/Tests/ProtonVPN.UI.Tests/Results/HomeResult.cs similarity index 94% rename from test/ProtonVPN.UI.Test/Results/HomeResult.cs rename to src/Tests/ProtonVPN.UI.Tests/Results/HomeResult.cs index 873e39e58..bac91e17a 100644 --- a/test/ProtonVPN.UI.Test/Results/HomeResult.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Results/HomeResult.cs @@ -22,11 +22,11 @@ using System.Threading.Tasks; using FlaUI.Core.AutomationElements; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.UI.Test.ApiClient; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.ApiClient; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Results +namespace ProtonVPN.UI.Tests.Results { public class HomeResult : UIActions { @@ -97,20 +97,20 @@ public HomeResult VerifyLoggedInAsTextIs(string objectName) public HomeResult CheckIfConnectButtonIsNotDisplayed() { - CheckIfDoesNotExistsByName("CONNECT"); + CheckIfDoesNotExistsByName("Connect"); return this; } public HomeResult CheckIfUpgradeRequiredModalIsShown() { - WaitUntilElementExistsByName("Upgrade", TestConstants.ShortTimeout); + WaitUntilElementExistsByName("Upgrade", TestConstants.VeryShortTimeout); CheckIfDisplayedByClassName("PlusCountries"); return this; } public HomeResult CheckIfUpgradeRequiredModalIsShownSecureCore() { - WaitUntilElementExistsByName("Upgrade", TestConstants.ShortTimeout); + WaitUntilElementExistsByName("Upgrade", TestConstants.VeryShortTimeout); CheckIfDisplayedByClassName("SecureCore"); return this; } @@ -129,7 +129,7 @@ public HomeResult CheckIfPortForwardingQuickSettingIsNotVisible() public HomeResult CheckIfPortForwardingQuickSettingIsVisible() { - WaitUntilDisplayedByAutomationId("PortForwardingButton", TestConstants.ShortTimeout); + WaitUntilDisplayedByAutomationId("PortForwardingButton", TestConstants.VeryShortTimeout); return this; } } diff --git a/test/ProtonVPN.UI.Test/Results/LoginResult.cs b/src/Tests/ProtonVPN.UI.Tests/Results/LoginResult.cs similarity index 90% rename from test/ProtonVPN.UI.Test/Results/LoginResult.cs rename to src/Tests/ProtonVPN.UI.Tests/Results/LoginResult.cs index 5a9d0c97d..05a79e99f 100644 --- a/test/ProtonVPN.UI.Test/Results/LoginResult.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Results/LoginResult.cs @@ -17,9 +17,9 @@ * along with ProtonVPN. If not, see . */ -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Results +namespace ProtonVPN.UI.Tests.Results { public class LoginResult : UIActions { @@ -27,7 +27,7 @@ public class LoginResult : UIActions public LoginResult CheckIfLoginErrorIsDisplayed() => WaitUntilElementExistsByClassName("LoginErrorView", TestConstants.MediumTimeout); - public LoginResult CheckIfLoginWindowIsDisplayed() => WaitUntilElementExistsByAutomationId("LoginInput", TestConstants.ShortTimeout); + public LoginResult CheckIfLoginWindowIsDisplayed() => WaitUntilElementExistsByAutomationId("LoginInput", TestConstants.VeryShortTimeout); public LoginResult CheckIfZeroAssignedConnectionsModalIsShown() { diff --git a/test/ProtonVPN.UI.Test/Results/ProfilesResult.cs b/src/Tests/ProtonVPN.UI.Tests/Results/ProfilesResult.cs similarity index 92% rename from test/ProtonVPN.UI.Test/Results/ProfilesResult.cs rename to src/Tests/ProtonVPN.UI.Tests/Results/ProfilesResult.cs index dca61d880..b9dbd3010 100644 --- a/test/ProtonVPN.UI.Test/Results/ProfilesResult.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Results/ProfilesResult.cs @@ -17,18 +17,18 @@ * along with ProtonVPN. If not, see . */ -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Results +namespace ProtonVPN.UI.Tests.Results { public class ProfilesResult : UIActions { - public ProfilesResult CheckIfProfileIsDisplayed(string profileName) => WaitUntilElementExistsByName(profileName, TestConstants.ShortTimeout); + public ProfilesResult CheckIfProfileIsDisplayed(string profileName) => WaitUntilElementExistsByName(profileName, TestConstants.VeryShortTimeout); public ProfilesResult CheckIfDefaultProfilesAreDisplayed() { - WaitUntilElementExistsByName("Fastest", TestConstants.ShortTimeout); - WaitUntilElementExistsByName("Random", TestConstants.ShortTimeout); + WaitUntilElementExistsByName("Fastest", TestConstants.VeryShortTimeout); + WaitUntilElementExistsByName("Random", TestConstants.VeryShortTimeout); return this; } diff --git a/test/ProtonVPN.UI.Test/Results/SettingsResult.cs b/src/Tests/ProtonVPN.UI.Tests/Results/SettingsResult.cs similarity index 59% rename from test/ProtonVPN.UI.Test/Results/SettingsResult.cs rename to src/Tests/ProtonVPN.UI.Tests/Results/SettingsResult.cs index 2a4174f55..35f4385c9 100644 --- a/test/ProtonVPN.UI.Test/Results/SettingsResult.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Results/SettingsResult.cs @@ -17,32 +17,26 @@ * along with ProtonVPN. If not, see . */ -using System.Net; -using System.Net.NetworkInformation; -using System.Threading; using FlaUI.Core.AutomationElements; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Results +namespace ProtonVPN.UI.Tests.Results { public class SettingsResult : UIActions { private CheckBox ModerateNatCheckBox => ElementByAutomationId("ModerateNatCheckbox").AsCheckBox(); - public SettingsResult CheckIfDnsAddressDoesNotMatch(string dnsAddress) - { - Assert.AreNotEqual(dnsAddress, GetDnsAddressForAdapter()); - return this; - } + private string WireguardDnsAdress => DnsUtils.GetDnsAddress("ProtonVPN"); + private string OpenVpnDnsAdress => DnsUtils.GetDnsAddress("ProtonVPN TUN"); public SettingsResult CheckIfSettingsAreDisplayed() { - WaitUntilElementExistsByName("Start Minimized", TestConstants.ShortTimeout); - WaitUntilElementExistsByName("Start on boot", TestConstants.ShortTimeout); - WaitUntilElementExistsByName("Connect on app start", TestConstants.ShortTimeout); - WaitUntilElementExistsByName("Show Notifications", TestConstants.ShortTimeout); - WaitUntilElementExistsByName("Early Access", TestConstants.ShortTimeout); + WaitUntilElementExistsByName("Start Minimized", TestConstants.VeryShortTimeout); + WaitUntilElementExistsByName("Start on boot", TestConstants.VeryShortTimeout); + WaitUntilElementExistsByName("Connect on app start", TestConstants.VeryShortTimeout); + WaitUntilElementExistsByName("Show Notifications", TestConstants.VeryShortTimeout); + WaitUntilElementExistsByName("Early Access", TestConstants.VeryShortTimeout); return this; } @@ -52,14 +46,15 @@ public SettingsResult CheckIfCustomDnsAddressWasNotAdded() return this; } - public SettingsResult CheckIfDnsAddressMatches(string dnsAddress) + public SettingsResult CheckIfDnsAddressMatches(string expectedDnsAddress) + { + Assert.IsTrue(DoesContainDnsAddress(expectedDnsAddress), DnsAdressErrorMessage(expectedDnsAddress)); + return this; + } + + public SettingsResult CheckIfDnsAddressDoesNotMatch(string expectedDnsAddress) { - if(GetDnsAddressForAdapter() == null) - { - //Sometimes windows does not set DNS address fast enough, so some delay might be needed. - Thread.Sleep(3000); - } - Assert.AreEqual(dnsAddress, GetDnsAddressForAdapter(), "Desired dns address " + dnsAddress + " does not match Windows dns address " + GetDnsAddressForAdapter()); + Assert.IsFalse(DoesContainDnsAddress(expectedDnsAddress), DnsAdressErrorMessage(expectedDnsAddress)); return this; } @@ -75,23 +70,14 @@ public SettingsResult CheckIfModerateNatIsDisabled() return this; } - private string GetDnsAddressForAdapter() + private string DnsAdressErrorMessage(string expectedDnsAddress) + { + return $"Wireguard dns address: {WireguardDnsAdress}. OpenVPN dns address: {OpenVpnDnsAdress}. Expected dns value: {expectedDnsAddress}"; + } + + private bool DoesContainDnsAddress(string expectedDnsAddress) { - string dnsAddress = null; - NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); - foreach (NetworkInterface adapter in adapters) - { - IPInterfaceProperties adapterProperties = adapter.GetIPProperties(); - IPAddressCollection dnsServers = adapterProperties.DnsAddresses; - if (adapter.Description.Contains("WireGuard Tunnel")) - { - foreach (IPAddress dns in dnsServers) - { - dnsAddress = dns.ToString(); - } - } - } - return dnsAddress; + return WireguardDnsAdress == expectedDnsAddress || OpenVpnDnsAdress == expectedDnsAddress; } } } diff --git a/test/ProtonVPN.UI.Test/Results/SysTrayResult.cs b/src/Tests/ProtonVPN.UI.Tests/Results/SysTrayResult.cs similarity index 96% rename from test/ProtonVPN.UI.Test/Results/SysTrayResult.cs rename to src/Tests/ProtonVPN.UI.Tests/Results/SysTrayResult.cs index cdf5df6a5..7bc47d187 100644 --- a/test/ProtonVPN.UI.Test/Results/SysTrayResult.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Results/SysTrayResult.cs @@ -21,9 +21,9 @@ using System.ServiceProcess; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Results +namespace ProtonVPN.UI.Tests.Results { internal class SysTrayResult : DesktopActions { diff --git a/test/ProtonVPN.UI.Test/TestSession.cs b/src/Tests/ProtonVPN.UI.Tests/TestSession.cs similarity index 81% rename from test/ProtonVPN.UI.Test/TestSession.cs rename to src/Tests/ProtonVPN.UI.Tests/TestSession.cs index bc6828ac8..ae45fd709 100644 --- a/test/ProtonVPN.UI.Test/TestSession.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestSession.cs @@ -20,21 +20,19 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.ServiceProcess; using System.Threading; using FlaUI.Core; using FlaUI.Core.AutomationElements; -using FlaUI.Core.Capturing; +using FlaUI.Core.Tools; using FlaUI.UIA3; -using NUnit.Framework; +using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Extensions; -using ProtonVPN.UI.Test.ApiClient; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.ApiClient; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test +namespace ProtonVPN.UI.Tests { public class TestSession { @@ -91,20 +89,24 @@ protected static void Cleanup() protected static void RefreshWindow() { - try - { - Window = App.GetMainWindow(new UIA3Automation(), TestConstants.MediumTimeout); - } - catch (Exception ex) + Window = null; + RetryResult retry = Retry.WhileNull( + () => { + try + { + Window = App.GetMainWindow(new UIA3Automation(), TestConstants.MediumTimeout); + } + catch (System.TimeoutException) + { + //Ignore + } + return Window; + }, + TestConstants.MediumTimeout, TestConstants.RetryInterval); + + if (!retry.Success) { - if(ex is COMException || ex is System.TimeoutException) - { - //Sometimes UI might be locked and framework does not know how to handle it - Thread.Sleep(3000); - Window = App.GetMainWindow(new UIA3Automation(), TestConstants.MediumTimeout); - return; - } - throw; + Assert.Fail($"Failed to refresh window in {TestConstants.MediumTimeout.Seconds} seconds."); } } @@ -117,6 +119,8 @@ protected static void KillProtonVpnProcess() { Process[] proc = Process.GetProcessesByName("ProtonVPN"); proc.ForEach(p => p.Kill()); + //Give some time to properly exit the app + Thread.Sleep(2000); } protected static void RestartFileExplorer() @@ -142,6 +146,5 @@ protected static void StartFileExplorer() process.Start(); } } - } } diff --git a/test/ProtonVPN.UI.Test/Tests/AccountTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/AccountTests.cs similarity index 94% rename from test/ProtonVPN.UI.Test/Tests/AccountTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/AccountTests.cs index d6efaeb0d..8511f1b55 100644 --- a/test/ProtonVPN.UI.Test/Tests/AccountTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/AccountTests.cs @@ -17,12 +17,12 @@ * along with ProtonVPN. If not, see . */ -using ProtonVPN.UI.Test.TestsHelper; using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] diff --git a/test/ProtonVPN.UI.Test/Tests/ConnectionTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/ConnectionTests.cs similarity index 92% rename from test/ProtonVPN.UI.Test/Tests/ConnectionTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/ConnectionTests.cs index c7d8a6308..ef1218535 100644 --- a/test/ProtonVPN.UI.Test/Tests/ConnectionTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/ConnectionTests.cs @@ -17,13 +17,14 @@ * along with ProtonVPN. If not, see . */ +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("Connection")] @@ -41,6 +42,7 @@ public class ConnectionTests : TestSession private const string GOOGLE_URL = "www.google.com"; [Test] + [Category("Smoke")] public async Task QuickConnectAndDisconnect() { TestCaseId = 221; @@ -89,6 +91,7 @@ public void ConnectByCountryList() } [Test] + [Category("Smoke")] public void ConnectToCreatedProfile() { TestCaseId = 21551; @@ -160,6 +163,7 @@ public void CheckIfConnectionIsRestoredToSameServerAfterAppKill() } [Test] + [Category("Smoke")] public void CheckIfAutoConnectConnectsAutomatically() { TestCaseId = 204; @@ -167,9 +171,13 @@ public void CheckIfAutoConnectConnectsAutomatically() _loginWindow.SignIn(TestUserData.GetPlusUser()); _homeWindow.NavigateToSettings(); _settingsWindow.DisableStartToTray() - .CloseSettings(); - KillProtonVpnProcess(); + .CloseSettings() + .ExitTheApp(); + + //Delay to allow app to properly exit + Thread.Sleep(2000); LaunchApp(); + _homeWindow.WaitUntilConnected(); TestRailClient.MarkTestsByStatus(); @@ -177,9 +185,14 @@ public void CheckIfAutoConnectConnectsAutomatically() _homeWindow.PressQuickConnectButton() .NavigateToSettings(); - _settingsWindow.ClickOnConnectOnBoot(); - KillProtonVpnProcess(); + _settingsWindow.ClickOnConnectOnBoot() + .CloseSettings() + .ExitTheApp(); + + //Delay to allow app to properly exit + Thread.Sleep(2000); LaunchApp(); + _homeWindow.WaitUntilDisconnected(); } @@ -200,6 +213,7 @@ public void ConnectAndDisconnectViaMap() } [Test] + [Category("Smoke")] public void CheckCustomDnsManipulation() { TestCaseId = 4578; @@ -235,6 +249,7 @@ public void CheckCustomDnsManipulation() } [Test] + [Category("Smoke")] public void CancelConnectionWhileConnecting() { TestCaseId = 227; @@ -246,6 +261,7 @@ public void CancelConnectionWhileConnecting() } [Test] + [Category("Smoke")] public void AppExitWithKillSwitchEnabled() { TestCaseId = 216; diff --git a/test/ProtonVPN.UI.Test/Tests/LoginLogoutTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/LoginLogoutTests.cs similarity index 85% rename from test/ProtonVPN.UI.Test/Tests/LoginLogoutTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/LoginLogoutTests.cs index d4cec1fb8..3ad5d3497 100644 --- a/test/ProtonVPN.UI.Test/Tests/LoginLogoutTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/LoginLogoutTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] @@ -34,6 +34,7 @@ public class LoginLogoutTests : TestSession private readonly HomeResult _homeResult = new HomeResult(); [Test] + [Category("Smoke")] public void LoginAsFreeUser() { TestCaseId = 231; @@ -73,6 +74,7 @@ public void LoginAsVisionaryUser() } [Test] + [Category("Smoke")] public void LoginUsingIncorrectCredentials() { TestCaseId = 232; @@ -82,6 +84,7 @@ public void LoginUsingIncorrectCredentials() } [Test] + [Category("Smoke")] public void SuccessfulLogout() { TestCaseId = 211; @@ -109,6 +112,17 @@ public void LoginWithZeroAssignedConnections() _loginResult.CheckIfZeroAssignedConnectionsModalIsShown(); } + [Test] + [Category("Smoke")] + public void TwoFactorLogin() + { + TestCaseId = 129440; + + _loginWindow.EnterCredentials(TestUserData.GetTwoFactorUser()) + .EnterTwoFactorCode(TestUserData.GetTwoFactorCode()); + _homeResult.CheckIfLoggedIn(); + } + [SetUp] public void TestInitialize() { diff --git a/test/ProtonVPN.UI.Test/Tests/NetshieldTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/NetshieldTests.cs similarity index 93% rename from test/ProtonVPN.UI.Test/Tests/NetshieldTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/NetshieldTests.cs index 5f4fc79d2..58a11f210 100644 --- a/test/ProtonVPN.UI.Test/Tests/NetshieldTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/NetshieldTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("Connection")] @@ -37,6 +37,7 @@ public class NetshieldTests : TestSession private const string NETSHIELD_LEVEL_TWO = "netshield-2.protonvpn.net"; [Test] + [Category("Smoke")] public void NetshieldLevelTwo() { TestCaseId = 4574; @@ -51,6 +52,7 @@ public void NetshieldLevelTwo() } [Test] + [Category("Smoke")] public void NetshieldLevelOne() { TestCaseId = 4573; @@ -65,6 +67,7 @@ public void NetshieldLevelOne() } [Test] + [Category("Smoke")] public void NetshieldOff() { TestCaseId = 146925; diff --git a/test/ProtonVPN.UI.Test/Tests/ProfileTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/ProfileTests.cs similarity index 96% rename from test/ProtonVPN.UI.Test/Tests/ProfileTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/ProfileTests.cs index f0f4268e5..57288c3a0 100644 --- a/test/ProtonVPN.UI.Test/Tests/ProfileTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/ProfileTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] @@ -82,6 +82,7 @@ public void TryToCreateSecureCoreProfile() } [Test] + [Category("Smoke")] public void TryToCreateP2PProfile() { TestCaseId = 21553; @@ -104,6 +105,7 @@ public void TryToCreateTorProfile() } [Test] + [Category("Smoke")] public void DeleteProfile() { TestCaseId = 239; diff --git a/test/ProtonVPN.UI.Test/Tests/SecureCoreTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/SecureCoreTests.cs similarity index 94% rename from test/ProtonVPN.UI.Test/Tests/SecureCoreTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/SecureCoreTests.cs index f9a8642e3..eca8206b4 100644 --- a/test/ProtonVPN.UI.Test/Tests/SecureCoreTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/SecureCoreTests.cs @@ -19,11 +19,11 @@ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("Connection")] @@ -35,6 +35,7 @@ public class SecureCoreTests : TestSession private readonly HomeResult _homeResult = new HomeResult(); [Test] + [Category("Smoke")] public void QuickConnectWhileSecureCoreIsEnabled() { TestCaseId = 255; diff --git a/test/ProtonVPN.UI.Test/Tests/SetUp.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/SetUp.cs similarity index 91% rename from test/ProtonVPN.UI.Test/Tests/SetUp.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/SetUp.cs index 93d48b186..f8f467433 100644 --- a/test/ProtonVPN.UI.Test/Tests/SetUp.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/SetUp.cs @@ -19,12 +19,12 @@ using System; using System.IO; -using NUnit.Framework; -using ProtonVPN.UI.Test.ApiClient; -using ProtonVPN.UI.Test.TestsHelper; using System.Reflection; +using NUnit.Framework; +using ProtonVPN.UI.Tests.ApiClient; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [SetUpFixture] public class SetUp : TestSession @@ -40,7 +40,7 @@ public void TestInitialize() Directory.SetCurrentDirectory(dir); TestRailClient = new TestRailApiClient(_testRailUrl, TestUserData.GetTestrailUser().Username, TestUserData.GetTestrailUser().Password); - if (!TestEnvironment.AreTestsRunningLocally()) + if (!TestEnvironment.AreTestsRunningLocally() || !TestEnvironment.IsWindows11()) { CreateTestRailTestRun(); } diff --git a/test/ProtonVPN.UI.Test/Tests/SettingsTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/SettingsTests.cs similarity index 93% rename from test/ProtonVPN.UI.Test/Tests/SettingsTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/SettingsTests.cs index 1debdbce9..9bc66d84e 100644 --- a/test/ProtonVPN.UI.Test/Tests/SettingsTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/SettingsTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] @@ -35,6 +35,7 @@ public class SettingsTests : TestSession private readonly HomeResult _homeResult = new HomeResult(); [Test] + [Category("Smoke")] public void CheckIfSettingsGeneralTabHasAllInfo() { TestCaseId = 21555; @@ -57,7 +58,7 @@ public void CheckIfInvalidDnsIsNotPermitted() [Test] public void CheckIfPortForwdingSettingGetsHidden() { - TestCaseId = 4580; + TestCaseId = 128748; _settingsWindow.NavigateToAdvancedTab() .ClickOnPortForwardingShortcutCheckBox() diff --git a/test/ProtonVPN.UI.Test/Tests/SupportTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/SupportTests.cs similarity index 92% rename from test/ProtonVPN.UI.Test/Tests/SupportTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/SupportTests.cs index 0334f4af1..b66fc80e1 100644 --- a/test/ProtonVPN.UI.Test/Tests/SupportTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/SupportTests.cs @@ -18,10 +18,10 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] @@ -32,6 +32,7 @@ public class SupportTests : TestSession private readonly BugReportWindow _bugReportWindow = new BugReportWindow(); [Test] + [Category("Smoke")] public void SendBugReport() { TestCaseId = 21554; @@ -43,6 +44,7 @@ public void SendBugReport() } [Test] + [Category("Smoke")] public void SendBugReportViaLoginScreen() { TestCaseId = 141591; diff --git a/test/ProtonVPN.UI.Test/Tests/SysTrayTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/SysTrayTests.cs similarity index 94% rename from test/ProtonVPN.UI.Test/Tests/SysTrayTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/SysTrayTests.cs index d8caae3b1..581b63830 100644 --- a/test/ProtonVPN.UI.Test/Tests/SysTrayTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/SysTrayTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("Connection")] @@ -34,6 +34,7 @@ internal class SysTrayTests : TestSession private SysTrayResult _trayResult = new SysTrayResult(); [Test] + [Category("Smoke")] public void QuickConnectUsingTray() { TestCaseId = 243; diff --git a/test/ProtonVPN.UI.Test/Tests/WindowElementsTests.cs b/src/Tests/ProtonVPN.UI.Tests/Tests/WindowElementsTests.cs similarity index 91% rename from test/ProtonVPN.UI.Test/Tests/WindowElementsTests.cs rename to src/Tests/ProtonVPN.UI.Tests/Tests/WindowElementsTests.cs index 802147971..18ac00953 100644 --- a/test/ProtonVPN.UI.Test/Tests/WindowElementsTests.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Tests/WindowElementsTests.cs @@ -18,11 +18,11 @@ */ using NUnit.Framework; -using ProtonVPN.UI.Test.Results; -using ProtonVPN.UI.Test.TestsHelper; -using ProtonVPN.UI.Test.Windows; +using ProtonVPN.UI.Tests.Results; +using ProtonVPN.UI.Tests.TestsHelper; +using ProtonVPN.UI.Tests.Windows; -namespace ProtonVPN.UI.Test.Tests +namespace ProtonVPN.UI.Tests.Tests { [TestFixture] [Category("UI")] diff --git a/src/Tests/ProtonVPN.UI.Tests/TestsHelper/DnsUtils.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/DnsUtils.cs new file mode 100644 index 000000000..8a0c4ebd1 --- /dev/null +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/DnsUtils.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Proton + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using System.Net; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using FlaUI.Core.Tools; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ProtonVPN.UI.Tests.TestsHelper +{ + public class DnsUtils + { + [DllImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache")] + public static extern uint DnsFlushResolverCache(); + + public static string GetDnsAddress(string adapterName) + { + string dnsAddress = null; + RetryResult retry = Retry.WhileNull( + () => { + dnsAddress = GetDnsAddressForAdapterByName(adapterName); + return dnsAddress; + }, + TestConstants.VeryShortTimeout, TestConstants.RetryInterval); + + if (!retry.Success) + { + dnsAddress = null; + } + return dnsAddress; + } + + private static string GetDnsAddressForAdapterByName(string adapterName) + { + string dnsAddress = null; + NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface adapter in adapters) + { + IPInterfaceProperties adapterProperties = adapter.GetIPProperties(); + IPAddressCollection dnsServers = adapterProperties.DnsAddresses; + if (adapter.Name.Equals(adapterName)) + { + foreach (IPAddress dns in dnsServers) + { + dnsAddress = dns.ToString(); + } + } + } + return dnsAddress; + } + } +} diff --git a/test/ProtonVPN.UI.Test/TestsHelper/TestConstants.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestConstants.cs similarity index 93% rename from test/ProtonVPN.UI.Test/TestsHelper/TestConstants.cs rename to src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestConstants.cs index 7d253e800..43e73dfb2 100644 --- a/test/ProtonVPN.UI.Test/TestsHelper/TestConstants.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestConstants.cs @@ -20,12 +20,12 @@ using System; using System.IO; -namespace ProtonVPN.UI.Test.TestsHelper +namespace ProtonVPN.UI.Tests.TestsHelper { public static class TestConstants { - public static TimeSpan ShortTimeout => TimeSpan.FromSeconds(5); - public static TimeSpan TrayElementTimeout => TimeSpan.FromSeconds(10); + public static TimeSpan VeryShortTimeout => TimeSpan.FromSeconds(5); + public static TimeSpan ShortTimeout => TimeSpan.FromSeconds(10); public static TimeSpan MediumTimeout => TimeSpan.FromSeconds(30); public static TimeSpan LongTimeout => TimeSpan.FromSeconds(60); public static string ProfileName => "@AutomationProfile"; diff --git a/test/ProtonVPN.UI.Test/TestsHelper/TestEnvironment.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestEnvironment.cs similarity index 88% rename from test/ProtonVPN.UI.Test/TestsHelper/TestEnvironment.cs rename to src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestEnvironment.cs index 42c0dfdb0..dbb86f3f1 100644 --- a/test/ProtonVPN.UI.Test/TestsHelper/TestEnvironment.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestEnvironment.cs @@ -21,7 +21,7 @@ using System.IO; using Castle.Core.Internal; -namespace ProtonVPN.UI.Test.TestsHelper +namespace ProtonVPN.UI.Tests.TestsHelper { public class TestEnvironment : TestSession { @@ -40,5 +40,10 @@ public static bool IsVideoRecorderPresent() { return File.Exists(TestConstants.PathToRecorder); } + + public static bool IsWindows11() + { + return Environment.OSVersion.Version.Build >= 22000; + } } } diff --git a/test/ProtonVPN.UI.Test/TestsHelper/TestUserData.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestUserData.cs similarity index 85% rename from test/ProtonVPN.UI.Test/TestsHelper/TestUserData.cs rename to src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestUserData.cs index e95259f79..ae0c1b3ba 100644 --- a/test/ProtonVPN.UI.Test/TestsHelper/TestUserData.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestUserData.cs @@ -18,8 +18,9 @@ */ using System; +using OtpNet; -namespace ProtonVPN.UI.Test.TestsHelper +namespace ProtonVPN.UI.Tests.TestsHelper { public class TestUserData { @@ -78,6 +79,19 @@ public static TestUserData GetIncorrectCredentialsUser() return new TestUserData("IncorrectUsername", "IncorrectPass"); } + public static TestUserData GetTwoFactorUser() + { + (string username, string password) = GetUsernameAndPassword("TWO_FACTOR_AUTH_USER"); + return new TestUserData(username, password); + } + + public static string GetTwoFactorCode() + { + string key = Environment.GetEnvironmentVariable("TWO_FA_KEY"); + Totp totp = new Totp(Base32Encoding.ToBytes(key)); + return totp.ComputeTotp(); + } + private static (string, string) GetUsernameAndPassword(string userType) { string str = Environment.GetEnvironmentVariable(userType); diff --git a/test/ProtonVPN.UI.Test/TestsHelper/TestsRecorder.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestsRecorder.cs similarity index 98% rename from test/ProtonVPN.UI.Test/TestsHelper/TestsRecorder.cs rename to src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestsRecorder.cs index 8e9ce0782..427545c44 100644 --- a/test/ProtonVPN.UI.Test/TestsHelper/TestsRecorder.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/TestsRecorder.cs @@ -23,7 +23,7 @@ using FlaUI.Core.Capturing; using NUnit.Framework; -namespace ProtonVPN.UI.Test.TestsHelper +namespace ProtonVPN.UI.Tests.TestsHelper { public class TestsRecorder { diff --git a/test/ProtonVPN.UI.Test/TestsHelper/VPNServiceHelper.cs b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/VPNServiceHelper.cs similarity index 98% rename from test/ProtonVPN.UI.Test/TestsHelper/VPNServiceHelper.cs rename to src/Tests/ProtonVPN.UI.Tests/TestsHelper/VPNServiceHelper.cs index 3dfc21af8..589394c13 100644 --- a/test/ProtonVPN.UI.Test/TestsHelper/VPNServiceHelper.cs +++ b/src/Tests/ProtonVPN.UI.Tests/TestsHelper/VPNServiceHelper.cs @@ -27,7 +27,7 @@ using ProtonVPN.Service.Contract.Settings; using ProtonVPN.Service.Contract.Vpn; -namespace ProtonVPN.UI.Test.TestsHelper +namespace ProtonVPN.UI.Tests.TestsHelper { public class VPNServiceHelper { diff --git a/test/ProtonVPN.UI.Test/UIActions.cs b/src/Tests/ProtonVPN.UI.Tests/UIActions.cs similarity index 88% rename from test/ProtonVPN.UI.Test/UIActions.cs rename to src/Tests/ProtonVPN.UI.Tests/UIActions.cs index 68752d19d..6cd6296bb 100644 --- a/test/ProtonVPN.UI.Test/UIActions.cs +++ b/src/Tests/ProtonVPN.UI.Tests/UIActions.cs @@ -22,9 +22,9 @@ using FlaUI.Core.Input; using FlaUI.Core.Tools; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test +namespace ProtonVPN.UI.Tests { public class UIActions : TestSession { @@ -117,12 +117,26 @@ protected dynamic WaitUntilElementExistsByClassName(string className, TimeSpan t protected dynamic WaitUntilTextMatchesByAutomationId(string automationId, TimeSpan time, string text, string timeoutMessage) { - Retry.WhileFalse( + AutomationElement element = null; + string elementText = null; + RetryResult retry = Retry.WhileFalse( () => { RefreshWindow(); - return ElementByAutomationId(automationId).AsLabel().Text == text; + element = Window.FindFirstDescendant(cf => cf.ByAutomationId(automationId)); + if(element != null) + { + elementText = element.AsLabel().Text; + return elementText == text; + } + return false; }, - time, TestConstants.RetryInterval, true, false, timeoutMessage); + time, TestConstants.RetryInterval); + + if (!retry.Success) + { + elementText = "ELEMENT_WAS_NOT_FOUND"; + Assert.Fail($"Expected text: '{text}' does not match {automationId} element text {elementText}"); + } return this; } @@ -175,19 +189,19 @@ protected dynamic MoveMouseToElement(AutomationElement element, int offsetX = 0, protected AutomationElement ElementByAutomationId(string automationId) { - WaitUntilElementExistsByAutomationId(automationId, TestConstants.ShortTimeout); + WaitUntilElementExistsByAutomationId(automationId, TestConstants.VeryShortTimeout); return Window.FindFirstDescendant(cf => cf.ByAutomationId(automationId)); } protected AutomationElement ElementByClassName(string className) { - WaitUntilElementExistsByClassName(className, TestConstants.ShortTimeout); + WaitUntilElementExistsByClassName(className, TestConstants.VeryShortTimeout); return Window.FindFirstDescendant(cf => cf.ByClassName(className)); } protected AutomationElement ElementByName(string name) { - WaitUntilElementExistsByName(name, TestConstants.ShortTimeout); + WaitUntilElementExistsByName(name, TestConstants.VeryShortTimeout); return Window.FindFirstDescendant(cf => cf.ByName(name)); } diff --git a/test/ProtonVPN.UI.Test/Windows/BugReportWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/BugReportWindow.cs similarity index 95% rename from test/ProtonVPN.UI.Test/Windows/BugReportWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/BugReportWindow.cs index e1b245fca..d33f72c16 100644 --- a/test/ProtonVPN.UI.Test/Windows/BugReportWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/BugReportWindow.cs @@ -19,9 +19,9 @@ using FlaUI.Core.AutomationElements; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { public class BugReportWindow : UIActions { @@ -30,7 +30,7 @@ public class BugReportWindow : UIActions public BugReportWindow FillBugReportForm(string bugType) { - WaitUntilElementExistsByName(bugType, TestConstants.ShortTimeout); + WaitUntilElementExistsByName(bugType, TestConstants.VeryShortTimeout); ElementByName(bugType).Click(); ContactUsButton.Click(); AutomationElement[] bugReportInputFields = Window.FindAllDescendants(cf => cf.ByAutomationId("AdornedTextBox")); diff --git a/test/ProtonVPN.UI.Test/Windows/HomeWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/HomeWindow.cs similarity index 95% rename from test/ProtonVPN.UI.Test/Windows/HomeWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/HomeWindow.cs index e28ae73f7..ad03507ce 100644 --- a/test/ProtonVPN.UI.Test/Windows/HomeWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/HomeWindow.cs @@ -19,9 +19,9 @@ using System.Threading; using FlaUI.Core.AutomationElements; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { public class HomeWindow : UIActions { @@ -31,7 +31,7 @@ public class HomeWindow : UIActions private Button BugReportButton => ElementByAutomationId("MenuReportBugButton").AsButton(); private Button AccountButton => ElementByAutomationId("MenuAccountButton").AsButton(); private Button ExitButton => ElementByAutomationId("MenuExitButton").AsButton(); - private AutomationElement ProfilesTab => ElementByName("Profiles"); + private Button ProfilesTab => ElementByAutomationId("SidebarProfilesButton").AsButton(); private AutomationElement Profile(string profileName) => ElementByName(profileName); private Button ConnectButton => FirstVisibleElementByName("Connect").AsButton(); private AutomationElement Country(string countryName) => ElementByName(countryName); @@ -53,6 +53,7 @@ public class HomeWindow : UIActions private AutomationElement NetshieldOff => ElementByClassName("Shield"); private AutomationElement NetshieldLevelOne => ElementByClassName("ShieldHalfFilled"); private AutomationElement NetshieldLevelTwo => ElementByClassName("ShieldFilled"); + private AutomationElement ChevronDown => ElementByClassName("ChevronDown"); public HomeWindow PressQuickConnectButton() { @@ -62,7 +63,7 @@ public HomeWindow PressQuickConnectButton() public HomeWindow NavigateToProfilesTab() { - ProfilesTab.Click(); + ProfilesTab.Invoke(); return this; } @@ -170,8 +171,8 @@ public HomeWindow CloseSecureCoreWarningModal() public HomeWindow MoveMouseOnCountry(string countryName) { - SearchInput.Enter(countryName); - MoveMouseToElement(Country(countryName)); + SearchInput.Text = countryName; + MoveMouseToElement(ChevronDown); return this; } @@ -204,7 +205,7 @@ public HomeWindow ConfirmExit() { WaitUntilElementExistsByAutomationIdAndReturnTheElement( "CloseButton", - TestConstants.ShortTimeout).AsButton().Invoke(); + TestConstants.VeryShortTimeout).AsButton().Invoke(); return this; } diff --git a/test/ProtonVPN.UI.Test/Windows/LoginWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/LoginWindow.cs similarity index 79% rename from test/ProtonVPN.UI.Test/Windows/LoginWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/LoginWindow.cs index 50bc070f3..7d0774a6c 100644 --- a/test/ProtonVPN.UI.Test/Windows/LoginWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/LoginWindow.cs @@ -18,9 +18,9 @@ */ using FlaUI.Core.AutomationElements; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { public class LoginWindow : UIActions { @@ -30,6 +30,7 @@ public class LoginWindow : UIActions private Button SkipButton => ElementByName("Skip").AsButton(); private Button HelpButton => ElementByAutomationId("HelpButton").AsButton(); private Button ReportAnIssueButton => ElementByAutomationId("ReportAnIssueButton").AsButton(); + private TextBox TwoFaInput => ElementByAutomationId("TwoFactorAuthInput").AsTextBox(); public HomeWindow SignIn(TestUserData user) { @@ -48,6 +49,15 @@ public LoginWindow EnterCredentials(TestUserData user) return this; } + public HomeWindow EnterTwoFactorCode(string twoFaCode) + { + WaitUntilDisplayedByAutomationId("TwoFactorAuthInput", TestConstants.LongTimeout); + TwoFaInput.Text = twoFaCode; + LoginButton.Invoke(); + WaitUntilElementExistsByName("Skip", TestConstants.LongTimeout); + return new HomeWindow(); + } + public BugReportWindow NavigateToBugReport() { HelpButton.Invoke(); diff --git a/test/ProtonVPN.UI.Test/Windows/ProfilesWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/ProfilesWindow.cs similarity index 92% rename from test/ProtonVPN.UI.Test/Windows/ProfilesWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/ProfilesWindow.cs index 3a62c095b..b9efbda50 100644 --- a/test/ProtonVPN.UI.Test/Windows/ProfilesWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/ProfilesWindow.cs @@ -18,9 +18,9 @@ */ using FlaUI.Core.AutomationElements; -using ProtonVPN.UI.Test.TestsHelper; +using ProtonVPN.UI.Tests.TestsHelper; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { public class ProfilesWindow : UIActions { @@ -69,7 +69,7 @@ public ProfilesWindow CreateStandartProfileWithoutCountry(string profileName) public ProfilesWindow ConnectToProfile(string profileName) { - WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Connect-{profileName}", TestConstants.ShortTimeout).AsButton().Invoke(); + WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Connect-{profileName}", TestConstants.VeryShortTimeout).AsButton().Invoke(); return this; } @@ -93,21 +93,21 @@ public ProfilesWindow SelectP2POption() public ProfilesWindow PressCreateNewProfile() { - WaitUntilElementExistsByAutomationId("NewProfileButton", TestConstants.ShortTimeout); + WaitUntilElementExistsByAutomationId("NewProfileButton", TestConstants.VeryShortTimeout); CreateProfileButton.Invoke(); return this; } public ProfilesWindow DeleteProfileByByName(string profileName) { - WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Delete-{profileName}", TestConstants.ShortTimeout).AsButton().Invoke(); + WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Delete-{profileName}", TestConstants.VeryShortTimeout).AsButton().Invoke(); ContinueButton.Invoke(); return this; } public ProfilesWindow EditProfileName(string oldProfileName, string newProfileName) { - WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Edit-{oldProfileName}", TestConstants.ShortTimeout).AsButton().AsButton().Invoke(); + WaitUntilElementExistsByAutomationIdAndReturnTheElement($"Edit-{oldProfileName}", TestConstants.VeryShortTimeout).AsButton().AsButton().Invoke(); ProfileNameInput.Enter(newProfileName); SaveButton.Click(); return this; diff --git a/test/ProtonVPN.UI.Test/Windows/SettingsWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs similarity index 99% rename from test/ProtonVPN.UI.Test/Windows/SettingsWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs index 6e4c1b8f0..ba9986c91 100644 --- a/test/ProtonVPN.UI.Test/Windows/SettingsWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs @@ -19,7 +19,7 @@ using FlaUI.Core.AutomationElements; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { public class SettingsWindow : UIActions { diff --git a/test/ProtonVPN.UI.Test/Windows/SysTrayWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/SysTrayWindow.cs similarity index 98% rename from test/ProtonVPN.UI.Test/Windows/SysTrayWindow.cs rename to src/Tests/ProtonVPN.UI.Tests/Windows/SysTrayWindow.cs index 1fd3441c3..eb6cb376e 100644 --- a/test/ProtonVPN.UI.Test/Windows/SysTrayWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/SysTrayWindow.cs @@ -22,7 +22,7 @@ using FlaUI.Core.AutomationElements; using ProtonVPN.Windows; -namespace ProtonVPN.UI.Test.Windows +namespace ProtonVPN.UI.Tests.Windows { internal class SysTrayWindow : DesktopActions { diff --git a/test/ProtonVPN.UI.Test/app.config b/src/Tests/ProtonVPN.UI.Tests/app.config similarity index 100% rename from test/ProtonVPN.UI.Test/app.config rename to src/Tests/ProtonVPN.UI.Tests/app.config index 24ade5681..6a344de47 100644 --- a/test/ProtonVPN.UI.Test/app.config +++ b/src/Tests/ProtonVPN.UI.Tests/app.config @@ -2,10 +2,6 @@ - - - - @@ -14,6 +10,10 @@ + + + + diff --git a/src/Tests/ProtonVPN.Update.Tests/Contracts/FileContractTest.cs b/src/Tests/ProtonVPN.Update.Tests/Contracts/FileContractTest.cs new file mode 100644 index 000000000..cc0e6d885 --- /dev/null +++ b/src/Tests/ProtonVPN.Update.Tests/Contracts/FileContractTest.cs @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN 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. + * + * ProtonVPN 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 ProtonVPN. If not, see . + */ + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtonVPN.Update.Contracts; + +namespace ProtonVPN.Update.Tests.Contracts +{ + [TestClass] + public class FileContractTest + { + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(null); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeTrue_WhenOther_IsSelf() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb"}; + + bool result = file.Equals(file); + + result.Should().BeTrue(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_IsNotFileContract() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(new { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeTrue_WhenOther_IsEqual() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeTrue(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Url_IsDifferent() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "http://ubiquito.com/abc.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Url_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenThis_Url_IsNull() + { + FileContract file = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeTrue_WhenBoth_Urls_AreNull() + { + FileContract file = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeTrue(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Sha512CheckSum_IsDifferent() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "abcdefghij", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Sha512CheckSum_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = null, Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenThis_Sha512CheckSum_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = null, Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeTrue_WhenBoth_Sha512CheckSum_AreNull() + { + FileContract file = new FileContract { Url = null, Sha512CheckSum = null, Arguments = "/qb" }; + FileContract other = new FileContract { Url = null, Sha512CheckSum = null, Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeTrue(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Arguments_IsDifferent() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenOther_Arguments_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeFalse_WhenThis_Arguments_IsNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; + + bool result = file.Equals(other); + + result.Should().BeFalse(); + } + + [TestMethod] + public void Equals_ShouldBeTrue_WhenBoth_Arguments_AreNull() + { + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; + FileContract other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; + + bool result = file.Equals(other); + + result.Should().BeTrue(); + } + } +} diff --git a/test/ProtonVPN.Update.Test/Files/Downloadable/DownloadableFileTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/DownloadableFileTest.cs similarity index 99% rename from test/ProtonVPN.Update.Test/Files/Downloadable/DownloadableFileTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/DownloadableFileTest.cs index 0b37bd9fd..edbabcc9a 100644 --- a/test/ProtonVPN.Update.Test/Files/Downloadable/DownloadableFileTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/DownloadableFileTest.cs @@ -29,11 +29,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.OS.Net.Http; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Files.Downloadable; using ProtonVPN.Update.Files.Validatable; -namespace ProtonVPN.Update.Test.Files.Downloadable +namespace ProtonVPN.Update.Tests.Files.Downloadable { [TestClass] public class DownloadableFileTest diff --git a/test/ProtonVPN.Update.Test/Files/Downloadable/SafeDownloadableFileTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/SafeDownloadableFileTest.cs similarity index 90% rename from test/ProtonVPN.Update.Test/Files/Downloadable/SafeDownloadableFileTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/SafeDownloadableFileTest.cs index feacd99fc..7d60069f9 100644 --- a/test/ProtonVPN.Update.Test/Files/Downloadable/SafeDownloadableFileTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Downloadable/SafeDownloadableFileTest.cs @@ -26,7 +26,7 @@ using NSubstitute; using ProtonVPN.Update.Files.Downloadable; -namespace ProtonVPN.Update.Test.Files.Downloadable +namespace ProtonVPN.Update.Tests.Files.Downloadable { [TestClass] public class SafeDownloadableFileTest @@ -44,7 +44,7 @@ public async Task Download_ShouldCall_Origin_Download_WithArguments() { const string uri = "Download uri"; const string filename = "File to launch"; - var downloadable = new SafeDownloadableFile(_origin); + SafeDownloadableFile downloadable = new SafeDownloadableFile(_origin); await downloadable.Download(uri, filename); @@ -55,7 +55,7 @@ public async Task Download_ShouldCall_Origin_Download_WithArguments() public void Launch_ShouldPassException_WhenOriginThrows() { _origin.WhenForAnyArgs(x => x.Download("", "")).Throw(); - var downloadable = new SafeDownloadableFile(_origin); + SafeDownloadableFile downloadable = new SafeDownloadableFile(_origin); Func action = () => downloadable.Download("", ""); @@ -82,7 +82,7 @@ private void Launch_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception ex { TestInitialize(); _origin.WhenForAnyArgs(x => x.Download("", "")).Throw(ex); - var downloadable = new SafeDownloadableFile(_origin); + SafeDownloadableFile downloadable = new SafeDownloadableFile(_origin); Func action = () => downloadable.Download("", ""); diff --git a/test/ProtonVPN.Update.Test/Files/FileLocationTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/FileLocationTest.cs similarity index 80% rename from test/ProtonVPN.Update.Test/Files/FileLocationTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/FileLocationTest.cs index 1a5437553..7991ee8d6 100644 --- a/test/ProtonVPN.Update.Test/Files/FileLocationTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/FileLocationTest.cs @@ -22,7 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Update.Files; -namespace ProtonVPN.Update.Test.Files +namespace ProtonVPN.Update.Tests.Files { [TestClass] public class FileLocationTest @@ -32,12 +32,12 @@ public void Path_ShouldBe_CombinedFromFolderAndFilename() { const string folder = @"C:\Windows\Temp\Downloads"; const string filename = "ProtonVPN_win_v3.3.3.exe"; - var url = $"https://the.proton.site/downloads/{filename}"; + string url = $"https://the.proton.site/downloads/{filename}"; - var expected = Path.Combine(folder, filename); - var location = new FileLocation(folder); + string expected = Path.Combine(folder, filename); + FileLocation location = new FileLocation(folder); - var result = location.Path(url); + string result = location.Path(url); result.Should().Be(expected); } diff --git a/test/ProtonVPN.Update.Test/Files/Launchable/LaunchableFileTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Launchable/LaunchableFileTest.cs similarity index 87% rename from test/ProtonVPN.Update.Test/Files/Launchable/LaunchableFileTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Launchable/LaunchableFileTest.cs index 99041bf9d..e931d53fc 100644 --- a/test/ProtonVPN.Update.Test/Files/Launchable/LaunchableFileTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Launchable/LaunchableFileTest.cs @@ -17,15 +17,15 @@ * along with ProtonVPN. If not, see . */ +using System; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using NSubstitute.ExceptionExtensions; using ProtonVPN.Common.OS.Processes; using ProtonVPN.Update.Files.Launchable; -using System; -namespace ProtonVPN.Update.Test.Files.Launchable +namespace ProtonVPN.Update.Tests.Files.Launchable { [TestClass] public class LaunchableFileTest @@ -43,7 +43,7 @@ public void Launch_ShouldCreate_ElevatedProcess() { const string filename = "File to launch"; const string args = "Launch arguments"; - var launchable = new LaunchableFile(_processes); + LaunchableFile launchable = new LaunchableFile(_processes); launchable.Launch(filename, args); @@ -53,7 +53,7 @@ public void Launch_ShouldCreate_ElevatedProcess() [TestMethod] public void Launch_ShouldStart_ElevatedProcess() { - var launchable = new LaunchableFile(_processes); + LaunchableFile launchable = new LaunchableFile(_processes); launchable.Launch("", ""); @@ -64,7 +64,7 @@ public void Launch_ShouldStart_ElevatedProcess() public void Valid_ShouldPassException_WhenOsProcesses_ElevatedProcess_Throws() { _processes.ElevatedProcess("", "").ThrowsForAnyArgs(); - var launchable = new LaunchableFile(_processes); + LaunchableFile launchable = new LaunchableFile(_processes); Action action = () => launchable.Launch("", ""); @@ -75,7 +75,7 @@ public void Valid_ShouldPassException_WhenOsProcesses_ElevatedProcess_Throws() public void Valid_ShouldPassException_WhenElevatedProcess_Start_Throws() { _processes.ElevatedProcess("", "").WhenForAnyArgs(x => x.Start()).Throws(); - var launchable = new LaunchableFile(_processes); + LaunchableFile launchable = new LaunchableFile(_processes); Action action = () => launchable.Launch("", ""); diff --git a/test/ProtonVPN.Update.Test/Files/Launchable/SafeLaunchableFileTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Launchable/SafeLaunchableFileTest.cs similarity index 88% rename from test/ProtonVPN.Update.Test/Files/Launchable/SafeLaunchableFileTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Launchable/SafeLaunchableFileTest.cs index cea450e60..03fa993e4 100644 --- a/test/ProtonVPN.Update.Test/Files/Launchable/SafeLaunchableFileTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Launchable/SafeLaunchableFileTest.cs @@ -24,7 +24,7 @@ using NSubstitute; using ProtonVPN.Update.Files.Launchable; -namespace ProtonVPN.Update.Test.Files.Launchable +namespace ProtonVPN.Update.Tests.Files.Launchable { [TestClass] public class SafeLaunchableFileTest @@ -42,7 +42,7 @@ public void Launch_ShouldCall_Origin_Launch_WithArguments() { const string filename = "File to launch"; const string args = "Launch arguments"; - var launchable = new SafeLaunchableFile(_origin); + SafeLaunchableFile launchable = new SafeLaunchableFile(_origin); launchable.Launch(filename, args); @@ -53,7 +53,7 @@ public void Launch_ShouldCall_Origin_Launch_WithArguments() public void Launch_ShouldPassException_WhenOriginThrows() { _origin.WhenForAnyArgs(x => x.Launch("", "")).Throw(); - var launchable = new SafeLaunchableFile(_origin); + SafeLaunchableFile launchable = new SafeLaunchableFile(_origin); Action action = () => launchable.Launch("", ""); @@ -68,7 +68,7 @@ public void Launch_ShouldThrow_AppUpdateException_WhenOriginThrows_ProcessExcept new Win32Exception() }; - foreach (var exception in exceptions) + foreach (Exception exception in exceptions) { Launch_ShouldThrow_AppUpdateException_WhenOriginThrows(exception); } @@ -78,7 +78,7 @@ private void Launch_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception ex { TestInitialize(); _origin.WhenForAnyArgs(x => x.Launch("", "")).Throw(ex); - var launchable = new SafeLaunchableFile(_origin); + SafeLaunchableFile launchable = new SafeLaunchableFile(_origin); Action action = () => launchable.Launch("", ""); diff --git a/test/ProtonVPN.Update.Test/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs similarity index 81% rename from test/ProtonVPN.Update.Test/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs index 6cace4065..28c4ad9ee 100644 --- a/test/ProtonVPN.Update.Test/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/UpdatesDirectory/SafeUpdatesDirectoryTest.cs @@ -26,7 +26,7 @@ using NSubstitute.ExceptionExtensions; using ProtonVPN.Update.Files.UpdatesDirectory; -namespace ProtonVPN.Update.Test.Files.UpdatesDirectory +namespace ProtonVPN.Update.Tests.Files.UpdatesDirectory { [TestClass] public class SafeUpdatesDirectoryTest @@ -43,11 +43,11 @@ public virtual void TestInitialize() [SuppressMessage("ReSharper", "UnusedVariable")] public void Path_ShouldGet_OriginPath() { - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); - var result = directory.Path; + string result = directory.Path; - var dummy = _origin.Received(1).Path; + string dummy = _origin.Received(1).Path; } [TestMethod] @@ -55,9 +55,9 @@ public void Path_ShouldBe_FromOrigin() { const string expected = "Expected path"; _origin.Path.Returns(expected); - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); - var result = directory.Path; + string result = directory.Path; result.Should().Be(expected); } @@ -67,9 +67,9 @@ public void Path_ShouldBe_FromOrigin() public void Path_ShouldPassException_WhenOriginThrows() { _origin.Path.Throws(); - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); - Action action = () => { var result = directory.Path; }; + Action action = () => { string result = directory.Path; }; action.Should().Throw(); } @@ -83,7 +83,7 @@ public void Path_ShouldThrow_AppUpdateException_WhenOriginThrows_FileAccessExcep new UnauthorizedAccessException() }; - foreach (var exception in exceptions) + foreach (Exception exception in exceptions) { Path_ShouldThrow_AppUpdateException_WhenOriginThrows(exception); } @@ -94,9 +94,9 @@ private void Path_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception ex) { TestInitialize(); _origin.Path.Throws(ex); - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); - Action action = () => { var result = directory.Path; }; + Action action = () => { string result = directory.Path; }; action.Should().Throw(); } @@ -104,7 +104,7 @@ private void Path_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception ex) [TestMethod] public void Cleanup_ShouldCall_Origin() { - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); directory.Cleanup(); @@ -115,7 +115,7 @@ public void Cleanup_ShouldCall_Origin() public void Cleanup_ShouldPassException_WhenOriginThrows() { _origin.When(x => x.Cleanup()).Do(_ => throw new SomeException()); - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); Action action = () => directory.Cleanup(); @@ -131,7 +131,7 @@ public void Cleanup_ShouldThrow_AppUpdateException_WhenOriginThrows_FileAccessEx new UnauthorizedAccessException() }; - foreach (var exception in exceptions) + foreach (Exception exception in exceptions) { Cleanup_ShouldThrow_AppUpdateException_WhenOriginThrows(exception); } @@ -141,7 +141,7 @@ private void Cleanup_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception e { TestInitialize(); _origin.When(x => x.Cleanup()).Do(_ => throw ex); - var directory = new SafeUpdatesDirectory(_origin); + SafeUpdatesDirectory directory = new(_origin); Action action = () => directory.Cleanup(); diff --git a/test/ProtonVPN.Update.Test/Files/Validatable/CachedFileValidatorTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/CachedFileValidatorTest.cs similarity index 99% rename from test/ProtonVPN.Update.Test/Files/Validatable/CachedFileValidatorTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Validatable/CachedFileValidatorTest.cs index 0577d95f9..145084673 100644 --- a/test/ProtonVPN.Update.Test/Files/Validatable/CachedFileValidatorTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/CachedFileValidatorTest.cs @@ -24,10 +24,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using NSubstitute.ExceptionExtensions; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Files.Validatable; -namespace ProtonVPN.Update.Test.Files.Validatable +namespace ProtonVPN.Update.Tests.Files.Validatable { [TestClass] public class CachedFileValidatorTest diff --git a/test/ProtonVPN.Update.Test/Files/Validatable/FileCheckSumTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileCheckSumTest.cs similarity index 94% rename from test/ProtonVPN.Update.Test/Files/Validatable/FileCheckSumTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileCheckSumTest.cs index f30592b12..ba775d8a2 100644 --- a/test/ProtonVPN.Update.Test/Files/Validatable/FileCheckSumTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileCheckSumTest.cs @@ -20,10 +20,10 @@ using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Files.Validatable; -namespace ProtonVPN.Update.Test.Files.Validatable +namespace ProtonVPN.Update.Tests.Files.Validatable { [TestClass] public class FileCheckSumTest diff --git a/test/ProtonVPN.Update.Test/Files/Validatable/FileValidatorTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileValidatorTest.cs similarity index 96% rename from test/ProtonVPN.Update.Test/Files/Validatable/FileValidatorTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileValidatorTest.cs index 6ea11f0d4..6ec5eb335 100644 --- a/test/ProtonVPN.Update.Test/Files/Validatable/FileValidatorTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/FileValidatorTest.cs @@ -20,10 +20,10 @@ using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Files.Validatable; -namespace ProtonVPN.Update.Test.Files.Validatable +namespace ProtonVPN.Update.Tests.Files.Validatable { [TestClass] public class FileValidatorTest diff --git a/test/ProtonVPN.Update.Test/Files/Validatable/SafeFileValidatorTest.cs b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/SafeFileValidatorTest.cs similarity index 98% rename from test/ProtonVPN.Update.Test/Files/Validatable/SafeFileValidatorTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Files/Validatable/SafeFileValidatorTest.cs index be6c2669f..910c49c8a 100644 --- a/test/ProtonVPN.Update.Test/Files/Validatable/SafeFileValidatorTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Files/Validatable/SafeFileValidatorTest.cs @@ -26,7 +26,7 @@ using NSubstitute.ExceptionExtensions; using ProtonVPN.Update.Files.Validatable; -namespace ProtonVPN.Update.Test.Files.Validatable +namespace ProtonVPN.Update.Tests.Files.Validatable { [TestClass] public class SafeFileValidatorTest diff --git a/test/ProtonVPN.Update.Test/Helpers/ExceptionExtensionsTest.cs b/src/Tests/ProtonVPN.Update.Tests/Helpers/ExceptionExtensionsTest.cs similarity index 91% rename from test/ProtonVPN.Update.Test/Helpers/ExceptionExtensionsTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Helpers/ExceptionExtensionsTest.cs index 8c9e0822d..d4e5ce9d9 100644 --- a/test/ProtonVPN.Update.Test/Helpers/ExceptionExtensionsTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Helpers/ExceptionExtensionsTest.cs @@ -26,7 +26,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Update.Helpers; -namespace ProtonVPN.Update.Test.Helpers +namespace ProtonVPN.Update.Tests.Helpers { [TestClass] public class ExceptionExtensionsTest @@ -46,9 +46,9 @@ public void IsCommunicationException_ShouldBe_When_Exception() new TestData(false, new Exception()), }; - foreach (var data in testData) + foreach (TestData data in testData) { - var result = data.Exception.IsCommunicationException(); + bool result = data.Exception.IsCommunicationException(); result.Should().Be(data.Expected, $"{data.Exception.GetType()} is not communication exception"); } @@ -69,9 +69,9 @@ public void IsProcessException_ShouldBe_When_Exception() new TestData(false, new Exception()), }; - foreach (var data in testData) + foreach (TestData data in testData) { - var result = data.Exception.IsProcessException(); + bool result = data.Exception.IsProcessException(); result.Should().Be(data.Expected, $"{data.Exception.GetType()} is not process exception"); } diff --git a/test/ProtonVPN.Update.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Update.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.Update.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Update.Tests/Properties/AssemblyInfo.cs index 3f60f43d8..34ea76bd7 100644 --- a/test/ProtonVPN.Update.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Update.Test")] +[assembly: AssemblyTitle("ProtonVPN.Update.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN")] +[assembly: AssemblyProduct("ProtonVPN.Update.Tests")] [assembly: AssemblyCopyright("Copyright © 2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/test/ProtonVPN.Update.Test/ProtonVPN.Update.Test.csproj b/src/Tests/ProtonVPN.Update.Tests/ProtonVPN.Update.Tests.csproj similarity index 92% rename from test/ProtonVPN.Update.Test/ProtonVPN.Update.Test.csproj rename to src/Tests/ProtonVPN.Update.Tests/ProtonVPN.Update.Tests.csproj index abb3826e3..eeec91590 100644 --- a/test/ProtonVPN.Update.Test/ProtonVPN.Update.Test.csproj +++ b/src/Tests/ProtonVPN.Update.Tests/ProtonVPN.Update.Tests.csproj @@ -6,8 +6,8 @@ {7D658974-C38C-421F-8186-B735F06CFC58} Library Properties - ProtonVPN.Update.Test - ProtonVPN.Update.Test + ProtonVPN.Update.Tests + ProtonVPN.Update.Tests v4.7.2 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -24,7 +24,7 @@ true full false - ..\..\src\bin\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -33,7 +33,7 @@ pdbonly true - ..\..\src\bin\ + ..\..\bin\ TRACE prompt 4 @@ -80,17 +80,17 @@ - + {03b8e43c-5680-4803-a745-0a104fe6620c} ProtonVPN.Common - + {90fdf2b3-25c9-428d-b264-5a5faeb2d988} ProtonVPN.Update - + {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common + ProtonVPN.Tests.Common diff --git a/test/ProtonVPN.Update.Test/Releases/ReleaseTest.cs b/src/Tests/ProtonVPN.Update.Tests/Releases/ReleaseTest.cs similarity index 56% rename from test/ProtonVPN.Update.Test/Releases/ReleaseTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Releases/ReleaseTest.cs index 5639fd420..d1f2af74e 100644 --- a/test/ProtonVPN.Update.Test/Releases/ReleaseTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Releases/ReleaseTest.cs @@ -18,12 +18,13 @@ */ using System; +using System.Collections.Generic; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Update.Contracts; using ProtonVPN.Update.Releases; -namespace ProtonVPN.Update.Test.Releases +namespace ProtonVPN.Update.Tests.Releases { [TestClass] public class ReleaseTest @@ -31,10 +32,10 @@ public class ReleaseTest [TestMethod] public void Version_ShouldBe_Release_Version() { - var contract = new ReleaseContract { Version = "3.2.1" }; - var release = new Release(contract, false, new Version()); + ReleaseContract contract = new ReleaseContract { Version = "3.2.1" }; + Release release = new Release(contract, false, new Version()); - var result = release.Version; + Version result = release.Version; result.Major.Should().Be(3); result.Minor.Should().Be(2); @@ -45,11 +46,11 @@ public void Version_ShouldBe_Release_Version() [TestMethod] public void ChangeLog_ShouldBe_Release_ChangeLog() { - var changeLog = new[] { "Super new changes.", "Super new feature.", "Fixed bugs!" }; - var contract = new ReleaseContract { Version = "3.2.1", ChangeLog = changeLog }; - var release = new Release(contract, false, new Version()); + string[] changeLog = new[] { "Super new changes.", "Super new feature.", "Fixed bugs!" }; + ReleaseContract contract = new ReleaseContract { Version = "3.2.1", ChangeLog = changeLog }; + Release release = new Release(contract, false, new Version()); - var result = release.ChangeLog; + IReadOnlyList result = release.ChangeLog; result.Should().ContainInOrder(changeLog); } @@ -59,10 +60,10 @@ public void ChangeLog_ShouldBe_Release_ChangeLog() [DataRow(true)] public void EarlyAccess_ShouldBe_EarlyAccess(bool expected) { - var contract = new ReleaseContract { Version = "1.1.1" }; - var release = new Release(contract, expected, new Version()); + ReleaseContract contract = new ReleaseContract { Version = "1.1.1" }; + Release release = new Release(contract, expected, new Version()); - var result = release.EarlyAccess; + bool result = release.EarlyAccess; result.Should().Be(expected); } @@ -74,10 +75,10 @@ public void EarlyAccess_ShouldBe_EarlyAccess(bool expected) [DataRow(true, "1.2.3", "10.10.10")] public void New_ShouldBe_WhenReleaseVersion_AndCurrentVersion(bool expected, string releaseVersion, string currentVersion) { - var contract = new ReleaseContract { Version = releaseVersion }; - var release = new Release(contract, expected, Version.Parse(currentVersion)); + ReleaseContract contract = new ReleaseContract { Version = releaseVersion }; + Release release = new Release(contract, expected, Version.Parse(currentVersion)); - var result = release.EarlyAccess; + bool result = release.EarlyAccess; result.Should().Be(expected); } @@ -85,11 +86,11 @@ public void New_ShouldBe_WhenReleaseVersion_AndCurrentVersion(bool expected, str [TestMethod] public void File_ShouldBe_Release_File() { - var file = new FileContract(); - var contract = new ReleaseContract { Version = "9.9.9", File = file }; - var release = new Release(contract, false, new Version()); + FileContract file = new FileContract(); + ReleaseContract contract = new ReleaseContract { Version = "9.9.9", File = file }; + Release release = new Release(contract, false, new Version()); - var result = release.File; + FileContract result = release.File; result.Should().BeSameAs(file); } @@ -97,11 +98,11 @@ public void File_ShouldBe_Release_File() [TestMethod] public void Empty_ShouldBeTrue_WhenReleaseVersion_IsZero() { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "012345", Arguments = "-install" }; - var contract = new ReleaseContract { Version = "0.0.0", ChangeLog = new[] { "Fixed" }, File = file }; - var release = new Release(contract, false, new Version()); + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "012345", Arguments = "-install" }; + ReleaseContract contract = new ReleaseContract { Version = "0.0.0", ChangeLog = new[] { "Fixed" }, File = file }; + Release release = new Release(contract, false, new Version()); - var result = release.Empty(); + bool result = release.Empty(); result.Should().BeTrue(); } @@ -109,11 +110,11 @@ public void Empty_ShouldBeTrue_WhenReleaseVersion_IsZero() [TestMethod] public void Empty_ShouldBeFalse_WhenReleaseVersion_IsNotZero() { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "012345", Arguments = "-install" }; - var contract = new ReleaseContract { Version = "0.0.1", ChangeLog = new[] { "Fixed" }, File = file }; - var release = new Release(contract, false, new Version()); + FileContract file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "012345", Arguments = "-install" }; + ReleaseContract contract = new ReleaseContract { Version = "0.0.1", ChangeLog = new[] { "Fixed" }, File = file }; + Release release = new Release(contract, false, new Version()); - var result = release.Empty(); + bool result = release.Empty(); result.Should().BeFalse(); } @@ -121,7 +122,7 @@ public void Empty_ShouldBeFalse_WhenReleaseVersion_IsNotZero() [TestMethod] public void EmptyRelease_Should_BeEmpty() { - var result = Release.EmptyRelease(); + Release result = Release.EmptyRelease(); result.Empty().Should().BeTrue(); } @@ -129,7 +130,7 @@ public void EmptyRelease_Should_BeEmpty() [TestMethod] public void EmptyRelease_ShouldHave_ZeroVersion() { - var result = Release.EmptyRelease(); + Release result = Release.EmptyRelease(); result.Version.Major.Should().Be(0); result.Version.Minor.Should().Be(0); @@ -140,7 +141,7 @@ public void EmptyRelease_ShouldHave_ZeroVersion() [TestMethod] public void Release_ShouldImplement_IComparable() { - var release = new Release(new ReleaseContract { Version = "1.2.3" }, false, new Version()); + Release release = new Release(new ReleaseContract { Version = "1.2.3" }, false, new Version()); release.Should().BeAssignableTo>(); } @@ -153,10 +154,10 @@ public void Release_ShouldImplement_IComparable() [DataRow(1, "10.20.30", "1.2.3")] public void CompareTo_ShouldCompare_ReleaseVersions(int expected, string version, string otherVersion) { - var release = new Release(new ReleaseContract { Version = version }, false, new Version()); - var other = new Release(new ReleaseContract { Version = otherVersion }, true, new Version()); + Release release = new Release(new ReleaseContract { Version = version }, false, new Version()); + Release other = new Release(new ReleaseContract { Version = otherVersion }, true, new Version()); - var result = release.CompareTo(other); + int result = release.CompareTo(other); result.Should().Be(expected); } diff --git a/test/ProtonVPN.Update.Test/Releases/ReleasesTest.cs b/src/Tests/ProtonVPN.Update.Tests/Releases/ReleasesTest.cs similarity index 98% rename from test/ProtonVPN.Update.Test/Releases/ReleasesTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Releases/ReleasesTest.cs index 24b1c8abc..a6d2925a5 100644 --- a/test/ProtonVPN.Update.Test/Releases/ReleasesTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Releases/ReleasesTest.cs @@ -27,7 +27,7 @@ using ProtonVPN.Update.Contracts; using ProtonVPN.Update.Releases; -namespace ProtonVPN.Update.Test.Releases +namespace ProtonVPN.Update.Tests.Releases { [TestClass] public class ReleasesTest diff --git a/test/ProtonVPN.Update.Test/Storage/OrderedReleaseStorageTest.cs b/src/Tests/ProtonVPN.Update.Tests/Storage/OrderedReleaseStorageTest.cs similarity index 86% rename from test/ProtonVPN.Update.Test/Storage/OrderedReleaseStorageTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Storage/OrderedReleaseStorageTest.cs index b4c1dc716..0dcdd14bb 100644 --- a/test/ProtonVPN.Update.Test/Storage/OrderedReleaseStorageTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Storage/OrderedReleaseStorageTest.cs @@ -18,6 +18,7 @@ */ using System; +using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,7 +27,7 @@ using ProtonVPN.Update.Releases; using ProtonVPN.Update.Storage; -namespace ProtonVPN.Update.Test.Storage +namespace ProtonVPN.Update.Tests.Storage { [TestClass] public class OrderedReleaseStorageTest @@ -42,7 +43,7 @@ public void TestInitialize() [TestMethod] public async Task Releases_ShouldCall_Origin_Releases() { - var storage = new OrderedReleaseStorage(_origin); + OrderedReleaseStorage storage = new OrderedReleaseStorage(_origin); await storage.Releases(); @@ -52,7 +53,7 @@ public async Task Releases_ShouldCall_Origin_Releases() [TestMethod] public async Task Releases_ShouldBe_InDescendingOrder() { - var releases = new[] + Release[] releases = new[] { new Release(new ReleaseContract {Version = "0.1.2"}, false, new Version()), new Release(new ReleaseContract {Version = "5.5.5"}, false, new Version()), @@ -61,9 +62,9 @@ public async Task Releases_ShouldBe_InDescendingOrder() new Release(new ReleaseContract {Version = "2.1.0"}, false, new Version()), }; _origin.Releases().Returns(releases); - var storage = new OrderedReleaseStorage(_origin); + OrderedReleaseStorage storage = new OrderedReleaseStorage(_origin); - var result = await storage.Releases(); + IEnumerable result = await storage.Releases(); result.Should().BeInDescendingOrder(); } diff --git a/test/ProtonVPN.Update.Test/Storage/SafeReleaseStorageTest.cs b/src/Tests/ProtonVPN.Update.Tests/Storage/SafeReleaseStorageTest.cs similarity index 88% rename from test/ProtonVPN.Update.Test/Storage/SafeReleaseStorageTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Storage/SafeReleaseStorageTest.cs index 5ba82b6be..6e77f63a3 100644 --- a/test/ProtonVPN.Update.Test/Storage/SafeReleaseStorageTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Storage/SafeReleaseStorageTest.cs @@ -29,7 +29,7 @@ using ProtonVPN.Update.Releases; using ProtonVPN.Update.Storage; -namespace ProtonVPN.Update.Test.Storage +namespace ProtonVPN.Update.Tests.Storage { [TestClass] public class SafeReleaseStorageTest @@ -45,7 +45,7 @@ public void TestInitialize() [TestMethod] public async Task Releases_ShouldCall_Origin_Releases() { - var storage = new SafeReleaseStorage(_origin); + SafeReleaseStorage storage = new SafeReleaseStorage(_origin); await storage.Releases(); @@ -56,7 +56,7 @@ public async Task Releases_ShouldCall_Origin_Releases() public void Releases_ShouldPassException_WhenOriginThrows() { _origin.When(x => x.Releases()).Throw(); - var storage = new SafeReleaseStorage(_origin); + SafeReleaseStorage storage = new SafeReleaseStorage(_origin); Func action = () => storage.Releases(); @@ -74,7 +74,7 @@ public void Releases_ShouldThrow_AppUpdateException_WhenOriginThrows() new JsonException() }; - foreach (var exception in exceptions) + foreach (Exception exception in exceptions) { Releases_ShouldThrow_AppUpdateException_WhenOriginThrows(exception); Releases_ShouldThrow_AppUpdateException_WhenOriginThrowsAsync(exception); @@ -85,7 +85,7 @@ private void Releases_ShouldThrow_AppUpdateException_WhenOriginThrows(Exception { TestInitialize(); _origin.When(x => x.Releases()).Throw(ex); - var storage = new SafeReleaseStorage(_origin); + SafeReleaseStorage storage = new SafeReleaseStorage(_origin); Func action = () => storage.Releases(); @@ -96,7 +96,7 @@ private void Releases_ShouldThrow_AppUpdateException_WhenOriginThrowsAsync(Excep { TestInitialize(); _origin.Releases().Returns(Task.FromException>(ex)); - var storage = new SafeReleaseStorage(_origin); + SafeReleaseStorage storage = new SafeReleaseStorage(_origin); Func action = () => storage.Releases(); diff --git a/test/ProtonVPN.Update.Test/Storage/WebReleaseStorageTest.cs b/src/Tests/ProtonVPN.Update.Tests/Storage/WebReleaseStorageTest.cs similarity index 99% rename from test/ProtonVPN.Update.Test/Storage/WebReleaseStorageTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Storage/WebReleaseStorageTest.cs index be94cb952..04a67e880 100644 --- a/test/ProtonVPN.Update.Test/Storage/WebReleaseStorageTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Storage/WebReleaseStorageTest.cs @@ -28,12 +28,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.OS.Net.Http; -using ProtonVPN.Test.Common; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Config; using ProtonVPN.Update.Releases; using ProtonVPN.Update.Storage; -namespace ProtonVPN.Update.Test.Storage +namespace ProtonVPN.Update.Tests.Storage { [TestClass] public class WebReleaseStorageTest diff --git a/test/ProtonVPN.Update.Test/TestData/Empty file.txt b/src/Tests/ProtonVPN.Update.Tests/TestData/Empty file.txt similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/Empty file.txt rename to src/Tests/ProtonVPN.Update.Tests/TestData/Empty file.txt diff --git a/test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.0.0.exe b/src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.0.0.exe similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.0.0.exe rename to src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.0.0.exe diff --git a/test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.0.exe b/src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.0.exe similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.0.exe rename to src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.0.exe diff --git a/test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.1.exe b/src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.1.exe similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.1.exe rename to src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.1.exe diff --git a/test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.2.exe b/src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.2.exe similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v1.5.2.exe rename to src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v1.5.2.exe diff --git a/test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v2.0.0.exe b/src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v2.0.0.exe similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/ProtonVPN_win_v2.0.0.exe rename to src/Tests/ProtonVPN.Update.Tests/TestData/ProtonVPN_win_v2.0.0.exe diff --git a/test/ProtonVPN.Update.Test/TestData/win-update.json b/src/Tests/ProtonVPN.Update.Tests/TestData/win-update.json similarity index 100% rename from test/ProtonVPN.Update.Test/TestData/win-update.json rename to src/Tests/ProtonVPN.Update.Tests/TestData/win-update.json diff --git a/test/ProtonVPN.Update.Test/Updates/AppUpdateTest.cs b/src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdateTest.cs similarity index 89% rename from test/ProtonVPN.Update.Test/Updates/AppUpdateTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdateTest.cs index f24a4b80d..d8d3e7635 100644 --- a/test/ProtonVPN.Update.Test/Updates/AppUpdateTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdateTest.cs @@ -17,15 +17,8 @@ * along with ProtonVPN. If not, see . */ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NSubstitute; -using ProtonVPN.Common.OS.Net.Http; -using ProtonVPN.Update.Config; -using ProtonVPN.Update.Files.Launchable; -using ProtonVPN.Update.Files.Validatable; -using ProtonVPN.Update.Updates; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -33,11 +26,19 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using ProtonVPN.Test.Common; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using ProtonVPN.Common.OS.Net.Http; +using ProtonVPN.Tests.Common; +using ProtonVPN.Update.Config; +using ProtonVPN.Update.Files.Launchable; +using ProtonVPN.Update.Files.Validatable; +using ProtonVPN.Update.Updates; // ReSharper disable ObjectCreationAsStatement -namespace ProtonVPN.Update.Test.Updates +namespace ProtonVPN.Update.Tests.Updates { [TestClass] public class AppUpdateTest @@ -98,7 +99,7 @@ private IAppUpdate AppUpdate() [TestMethod] public void ReleaseHistory_ShouldBe_Empty_Initially() { - var update = AppUpdate(); + IAppUpdate update = AppUpdate(); update.ReleaseHistory().Should().BeEmpty(); } @@ -106,10 +107,10 @@ public void ReleaseHistory_ShouldBe_Empty_Initially() [TestMethod] public async Task ReleaseHistory_ShouldReturn_Stable_Releases() { - var update = AppUpdate(HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(HttpResponseFromFile("win-update.json")); update = await update.Latest(false); - var result = update.ReleaseHistory(); + IReadOnlyList result = update.ReleaseHistory(); result.Should() .HaveCount(3).And @@ -119,10 +120,10 @@ public async Task ReleaseHistory_ShouldReturn_Stable_Releases() [TestMethod] public async Task ReleaseHistory_ShouldReturn_StableAndEarlyAccess_Releases() { - var update = AppUpdate(HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(HttpResponseFromFile("win-update.json")); update = await update.Latest(true); - var result = update.ReleaseHistory(); + IReadOnlyList result = update.ReleaseHistory(); result.Should() .HaveCount(5).And @@ -132,10 +133,10 @@ public async Task ReleaseHistory_ShouldReturn_StableAndEarlyAccess_Releases() [TestMethod] public async Task ReleaseHistory_ShouldReturn_StableAndEarlyAccess_Releases_UpToCurrentVersion() { - var update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); update = await update.Latest(false); - var result = update.ReleaseHistory(); + IReadOnlyList result = update.ReleaseHistory(); result.Should() .HaveCount(4).And @@ -146,10 +147,10 @@ public async Task ReleaseHistory_ShouldReturn_StableAndEarlyAccess_Releases_UpTo [TestMethod] public async Task ReleaseHistory_ShouldReturn_Releases_WithChangeLog() { - var update = AppUpdate(HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(HttpResponseFromFile("win-update.json")); update = await update.Latest(false); - var result = update.ReleaseHistory(); + IReadOnlyList result = update.ReleaseHistory(); result[0].ChangeLog.Should() .HaveCount(2).And @@ -159,11 +160,11 @@ public async Task ReleaseHistory_ShouldReturn_Releases_WithChangeLog() [TestMethod] public async Task ReleaseHistory_ShouldReturn_Releases_OrderedByVersion() { - var update = AppUpdate(HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(HttpResponseFromFile("win-update.json")); update = await update.Latest(true); - var result = update.ReleaseHistory(); - var expected = result.OrderByDescending(r => r.Version); + IReadOnlyList result = update.ReleaseHistory(); + IOrderedEnumerable expected = result.OrderByDescending(r => r.Version); result.Should().ContainInOrder(expected); } @@ -175,7 +176,7 @@ public async Task ReleaseHistory_ShouldReturn_Releases_OrderedByVersion() [TestMethod] public void Available_ShouldBeFalse_Initially() { - var update = AppUpdate(); + IAppUpdate update = AppUpdate(); update.Available.Should().BeFalse(); } @@ -282,7 +283,7 @@ public async Task Available_ShouldNotChange_AfterDownload_WhenItWasTrue() [TestMethod] public void Ready_ShouldBeFalse_Initially() { - var update = AppUpdate(); + IAppUpdate update = AppUpdate(); update.Ready.Should().BeFalse(); } @@ -290,11 +291,11 @@ public void Ready_ShouldBeFalse_Initially() [TestMethod] public async Task Ready_ShouldBeFalse_AfterLatest_WhenUpdateNotAvailable() { - var update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); update.Available.Should().BeTrue(); - var response = HttpResponseFromFile("win-update.json"); + IHttpResponseMessage response = HttpResponseFromFile("win-update.json"); _httpClient.GetAsync(_config.FeedUriProvider.GetFeedUrl()).Returns(response); update = await update.Latest(false); @@ -306,7 +307,7 @@ public async Task Ready_ShouldBeFalse_AfterLatest_WhenUpdateNotAvailable() [TestMethod] public async Task Ready_ShouldBeFalse_AfterCachedLatest_WhenUpdateNotAvailable() { - var update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); update.Available.Should().BeTrue(); @@ -319,7 +320,7 @@ public async Task Ready_ShouldBeFalse_AfterCachedLatest_WhenUpdateNotAvailable() [TestMethod] public async Task Ready_ShouldNotChange_AfterDownloaded_WhenNotAvailable() { - var update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); update = await update.Latest(false); update.Available.Should().BeFalse(); @@ -331,10 +332,10 @@ public async Task Ready_ShouldNotChange_AfterDownloaded_WhenNotAvailable() [TestMethod] public async Task Ready_ShouldNotChange_AfterDownloaded_WhenFalse() { - var httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); + IHttpResponseMessage httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); _httpClient.GetAsync("https://protonvpn.com/download/ProtonVPN_win_v2.0.0.exe").Returns(httpResponse); - var update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); update.Ready.Should().BeFalse(); @@ -346,10 +347,10 @@ public async Task Ready_ShouldNotChange_AfterDownloaded_WhenFalse() [TestMethod] public async Task Ready_ShouldNotChange_AfterDownloaded_WhenTrue() { - var httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); + IHttpResponseMessage httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); _httpClient.GetAsync("https://protonvpn.com/download/ProtonVPN_win_v2.0.0.exe").Returns(httpResponse); - var update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 5), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); update = await update.Downloaded(); update = await update.Validated(); @@ -378,10 +379,10 @@ public async Task Ready_ShouldBeTrue_AfterValidated_WhenFileAlreadyExists() [TestMethod] public async Task Ready_ShouldBeFalse_AfterValidated_WhenFileCheckSum_IsNotValid() { - var httpResponse = HttpResponseFromFile("ProtonVPN_win_v1.0.0.exe"); + IHttpResponseMessage httpResponse = HttpResponseFromFile("ProtonVPN_win_v1.0.0.exe"); _httpClient.GetAsync("https://protonvpn.com/download/ProtonVPN_win_v1.5.1.exe").Returns(httpResponse); - var update = AppUpdate(new Version(1, 2, 0), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 2, 0), HttpResponseFromFile("win-update.json")); update = await update.Latest(false); update.Available.Should().BeTrue(); @@ -394,10 +395,10 @@ public async Task Ready_ShouldBeFalse_AfterValidated_WhenFileCheckSum_IsNotValid [TestMethod] public async Task Ready_ShouldBeTrue_AfterValidated_WhenFileCheckSum_IsValid() { - var httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); + IHttpResponseMessage httpResponse = HttpResponseFromFile("ProtonVPN_win_v2.0.0.exe"); _httpClient.GetAsync("https://protonvpn.com/download/ProtonVPN_win_v2.0.0.exe").Returns(httpResponse); - var update = AppUpdate(new Version(1, 5, 1), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 1), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); update.Available.Should().BeTrue(); @@ -497,7 +498,7 @@ public void Latest_ShouldThrow_WhenHttpResponse_IsNotJson() [TestMethod] public async Task CachedLatest_ShouldNotGet_JsonFile() { - var update = AppUpdate(HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(HttpResponseFromFile("win-update.json")); update.CachedLatest(false); @@ -507,9 +508,9 @@ public async Task CachedLatest_ShouldNotGet_JsonFile() [TestMethod] public async Task CachedLatest_ShouldNotChange_Releases_WhenEarlyAccess_IsFalse() { - var update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); update = await update.Latest(false); - var expected = update.ReleaseHistory().ToList(); + List expected = update.ReleaseHistory().ToList(); update = update.CachedLatest(false); @@ -520,9 +521,9 @@ public async Task CachedLatest_ShouldNotChange_Releases_WhenEarlyAccess_IsFalse( [TestMethod] public async Task CachedLatest_ShouldNotChange_Releases_WhenEarlyAccess_IsTrue() { - var update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); + IAppUpdate update = AppUpdate(new Version(1, 5, 2), HttpResponseFromFile("win-update.json")); update = await update.Latest(true); - var expected = update.ReleaseHistory().ToList(); + List expected = update.ReleaseHistory().ToList(); update = update.CachedLatest(true); @@ -683,7 +684,7 @@ private static Task FailedHttpRequest(Exception e) private static IHttpResponseMessage HttpResponseFromFile(string filePath) { MemoryStream stream = new(); - using (var inputStream = new FileStream(TestConfig.GetFolderPath(filePath), FileMode.Open)) + using (FileStream inputStream = new FileStream(TestConfig.GetFolderPath(filePath), FileMode.Open)) { inputStream.CopyTo(stream); inputStream.Flush(); diff --git a/test/ProtonVPN.Update.Test/Updates/AppUpdatesTest.cs b/src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdatesTest.cs similarity index 99% rename from test/ProtonVPN.Update.Test/Updates/AppUpdatesTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdatesTest.cs index 12a34f9f7..8d785cd3d 100644 --- a/test/ProtonVPN.Update.Test/Updates/AppUpdatesTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Updates/AppUpdatesTest.cs @@ -17,22 +17,22 @@ * along with ProtonVPN. If not, see . */ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.OS.Net.Http; +using ProtonVPN.Tests.Common; using ProtonVPN.Update.Config; using ProtonVPN.Update.Files.Launchable; using ProtonVPN.Update.Updates; -using System; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using ProtonVPN.Test.Common; // ReSharper disable ObjectCreationAsStatement -namespace ProtonVPN.Update.Test.Updates +namespace ProtonVPN.Update.Tests.Updates { [TestClass] public class AppUpdatesTest diff --git a/test/ProtonVPN.Update.Test/Updates/SafeAppUpdatesTest.cs b/src/Tests/ProtonVPN.Update.Tests/Updates/SafeAppUpdatesTest.cs similarity index 89% rename from test/ProtonVPN.Update.Test/Updates/SafeAppUpdatesTest.cs rename to src/Tests/ProtonVPN.Update.Tests/Updates/SafeAppUpdatesTest.cs index 18f330ce5..eef54d813 100644 --- a/test/ProtonVPN.Update.Test/Updates/SafeAppUpdatesTest.cs +++ b/src/Tests/ProtonVPN.Update.Tests/Updates/SafeAppUpdatesTest.cs @@ -17,17 +17,17 @@ * along with ProtonVPN. If not, see . */ +using System; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.Logging; -using ProtonVPN.Update.Updates; -using System; using ProtonVPN.Common.Logging.Categorization.Events.AppUpdateLogs; +using ProtonVPN.Update.Updates; // ReSharper disable ObjectCreationAsStatement -namespace ProtonVPN.Update.Test.Updates +namespace ProtonVPN.Update.Tests.Updates { [TestClass] public class SafeAppUpdatesTest @@ -71,7 +71,7 @@ public void SafeAppUpdates_ShouldTrow_WhenOrigin_IsNull() [TestMethod] public void Cleanup_ShouldCall_Origin_Cleanup() { - var updates = new SafeAppUpdates(_logger, _appUpdates); + SafeAppUpdates updates = new SafeAppUpdates(_logger, _appUpdates); updates.Cleanup(); @@ -82,7 +82,7 @@ public void Cleanup_ShouldCall_Origin_Cleanup() public void Cleanup_ShouldSuppress_AppUpdateException() { _appUpdates.When(x => x.Cleanup()).Do(x => throw new AppUpdateException("")); - var updates = new SafeAppUpdates(_logger, _appUpdates); + SafeAppUpdates updates = new SafeAppUpdates(_logger, _appUpdates); Action action = () => updates.Cleanup(); @@ -93,7 +93,7 @@ public void Cleanup_ShouldSuppress_AppUpdateException() public void Cleanup_ShouldLog_AppUpdateException() { _appUpdates.When(x => x.Cleanup()).Do(x => throw new AppUpdateException("")); - var updates = new SafeAppUpdates(_logger, _appUpdates); + SafeAppUpdates updates = new SafeAppUpdates(_logger, _appUpdates); updates.Cleanup(); @@ -104,7 +104,7 @@ public void Cleanup_ShouldLog_AppUpdateException() public void Cleanup_ShouldPass_Exception() { _appUpdates.When(x => x.Cleanup()).Do(x => throw new SomeException()); - var updates = new SafeAppUpdates(_logger, _appUpdates); + SafeAppUpdates updates = new SafeAppUpdates(_logger, _appUpdates); Action action = () => updates.Cleanup(); diff --git a/test/ProtonVPN.Vpn.Test/Connection/HandlingRequestsWrapperTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/Connection/HandlingRequestsWrapperTest.cs similarity index 99% rename from test/ProtonVPN.Vpn.Test/Connection/HandlingRequestsWrapperTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/Connection/HandlingRequestsWrapperTest.cs index 995556f70..6c0f32e32 100644 --- a/test/ProtonVPN.Vpn.Test/Connection/HandlingRequestsWrapperTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/Connection/HandlingRequestsWrapperTest.cs @@ -33,7 +33,7 @@ using ProtonVPN.Vpn.Common; using ProtonVPN.Vpn.Connection; -namespace ProtonVPN.Vpn.Test.Connection +namespace ProtonVPN.Vpn.Tests.Connection { [TestClass] public class HandlingRequestsWrapperTest diff --git a/test/ProtonVPN.Vpn.Test/Management/ManagementErrorTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/Management/ManagementErrorTest.cs similarity index 92% rename from test/ProtonVPN.Vpn.Test/Management/ManagementErrorTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/Management/ManagementErrorTest.cs index 2d5537e20..5683d7147 100644 --- a/test/ProtonVPN.Vpn.Test/Management/ManagementErrorTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/Management/ManagementErrorTest.cs @@ -22,7 +22,7 @@ using ProtonVPN.Common.Vpn; using ProtonVPN.Vpn.Management; -namespace ProtonVPN.Vpn.Test.Management +namespace ProtonVPN.Vpn.Tests.Management { [TestClass] public class ManagementErrorTest @@ -32,10 +32,10 @@ public void Message_ShouldBe_ManagementError_Message() { // Arrange const string expected = "Error!"; - var error = new ManagementError(expected); + ManagementError error = new ManagementError(expected); // Act - var result = error.Message; + string result = error.Message; // Assert result.Should().Be(expected); @@ -60,10 +60,10 @@ public void Message_ShouldBe_ManagementError_Message() public void VpnError_ShouldBe(VpnError expected, string message) { // Arrange - var error = new ManagementError(message); + ManagementError error = new ManagementError(message); // Act - var result = error.VpnError(); + VpnError result = error.VpnError(); // Assert result.Should().Be(expected); @@ -82,7 +82,7 @@ public void VpnError_ShouldBe(VpnError expected, string message) public void ContainsError_ShouldBe(bool expected, string message) { // Act - var result = ManagementError.ContainsError(message); + bool result = ManagementError.ContainsError(message); // Assert result.Should().Be(expected); diff --git a/test/ProtonVPN.Vpn.Test/Management/OpenVpnManagementPortsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/Management/OpenVpnManagementPortsTest.cs similarity index 91% rename from test/ProtonVPN.Vpn.Test/Management/OpenVpnManagementPortsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/Management/OpenVpnManagementPortsTest.cs index 2597a11d6..62a36d75c 100644 --- a/test/ProtonVPN.Vpn.Test/Management/OpenVpnManagementPortsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/Management/OpenVpnManagementPortsTest.cs @@ -21,7 +21,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Vpn.Management; -namespace ProtonVPN.Vpn.Test.Management +namespace ProtonVPN.Vpn.Tests.Management { [TestClass] public class OpenVpnManagementPortsTest @@ -30,7 +30,7 @@ public class OpenVpnManagementPortsTest public void Port_ShouldBeBetween_54000_And_59999() { // Arrange - var ports = new OpenVpnManagementPorts(); + OpenVpnManagementPorts ports = new OpenVpnManagementPorts(); // Act int result = ports.Port(); // Assert diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/BasicArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/BasicArgumentsTest.cs similarity index 81% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/BasicArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/BasicArgumentsTest.cs index 5d1230014..8bdeaa5ea 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/BasicArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/BasicArgumentsTest.cs @@ -17,6 +17,7 @@ * along with ProtonVPN. If not, see . */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -24,7 +25,7 @@ using ProtonVPN.Common.Configuration; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class BasicArgumentsTest @@ -41,10 +42,10 @@ public void TestInitialize() public void Enumerable_ShouldContain_ExpectedNumberOfOptions() { // Arrange - var subject = new BasicArguments(_config); + BasicArguments subject = new BasicArguments(_config); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().HaveCount(3); @@ -57,10 +58,10 @@ public void Enumerable_ShouldContain_ConfigOption() // Arrange _config.ConfigPath = configPath; - var subject = new BasicArguments(_config); + BasicArguments subject = new BasicArguments(_config); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--config \"{configPath}\""); @@ -70,10 +71,10 @@ public void Enumerable_ShouldContain_ConfigOption() public void Enumerable_ShouldContain_SuppressTimestampsOption() { // Arrange - var subject = new BasicArguments(_config); + BasicArguments subject = new BasicArguments(_config); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain("--suppress-timestamps"); @@ -86,10 +87,10 @@ public void Enumerable_ShouldContain_ServiceOption() // Arrange _config.ExitEventName = exitEventName; - var subject = new BasicArguments(_config); + BasicArguments subject = new BasicArguments(_config); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--service {exitEventName} 0"); diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/CommandLineArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/CommandLineArgumentsTest.cs similarity index 69% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/CommandLineArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/CommandLineArgumentsTest.cs index faf70eb92..4a2be9045 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/CommandLineArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/CommandLineArgumentsTest.cs @@ -21,7 +21,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class CommandLineArgumentsTest @@ -30,17 +30,17 @@ public class CommandLineArgumentsTest public void ToString_ShouldBe_JoinedStringOfAllArguments() { // Arrange - var args1 = new[] { "arg1", "arg3" }; - var args2 = new[] { "arg2" }; - var args3 = new[] { "arg10", "\"arg11\"", "--arg20" }; + string[] args1 = new[] { "arg1", "arg3" }; + string[] args2 = new[] { "arg2" }; + string[] args3 = new[] { "arg10", "\"arg11\"", "--arg20" }; - var subject = new CommandLineArguments() - .Add(args1) - .Add(args2) - .Add(args3); + CommandLineArguments subject = new CommandLineArguments() + .Add(args1) + .Add(args2) + .Add(args3); // Act - var result = subject.ToString(); + string result = subject.ToString(); // Assert result.Should().Be("arg1 arg3 arg2 arg10 \"arg11\" --arg20"); diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/EndpointArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/EndpointArgumentsTest.cs similarity index 98% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/EndpointArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/EndpointArgumentsTest.cs index 6e3237516..6c3139c0b 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/EndpointArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/EndpointArgumentsTest.cs @@ -28,7 +28,7 @@ using ProtonVPN.Vpn.Common; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] [SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")] diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/LocalHostArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/LocalHostArgumentsTest.cs similarity index 80% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/LocalHostArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/LocalHostArgumentsTest.cs index ec953f05c..0aeb6fe25 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/LocalHostArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/LocalHostArgumentsTest.cs @@ -17,12 +17,13 @@ * along with ProtonVPN. If not, see . */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class LocalHostArgumentsTest @@ -31,10 +32,10 @@ public class LocalHostArgumentsTest public void Enumerable_ShouldContain_ExpectedNumberOfOptions() { // Arrange - var subject = new LocalHostArguments("44.55.66.77"); + LocalHostArguments subject = new("44.55.66.77"); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().HaveCount(2); @@ -46,10 +47,10 @@ public void Enumerable_ShouldContain_LocalOption() const string localIp = "192.168.0.15"; // Arrange - var subject = new LocalHostArguments(localIp); + LocalHostArguments subject = new(localIp); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--local {localIp}"); @@ -59,10 +60,10 @@ public void Enumerable_ShouldContain_LocalOption() public void Enumerable_ShouldContain_LPortOption() { // Arrange - var subject = new LocalHostArguments("1.2.3.4"); + LocalHostArguments subject = new("1.2.3.4"); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--lport 0"); diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/ManagementArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/ManagementArgumentsTest.cs similarity index 80% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/ManagementArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/ManagementArgumentsTest.cs index b84bf64a7..b81738429 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/ManagementArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/ManagementArgumentsTest.cs @@ -17,6 +17,7 @@ * along with ProtonVPN. If not, see . */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -24,7 +25,7 @@ using ProtonVPN.Common.Configuration; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class ManagementArgumentsTest @@ -41,10 +42,10 @@ public void TestInitialize() public void Enumerable_ShouldContain_ExpectedNumberOfOptions() { // Arrange - var subject = new ManagementArguments(_config, 333); + ManagementArguments subject = new(_config, 333); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().HaveCount(3); @@ -57,10 +58,10 @@ public void Enumerable_ShouldContain_ManagementOption() // Arrange _config.ManagementHost = "127.0.0.5"; - var subject = new ManagementArguments(_config, managementPort); + ManagementArguments subject = new(_config, managementPort); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--management {_config.ManagementHost} {managementPort} stdin"); @@ -70,10 +71,10 @@ public void Enumerable_ShouldContain_ManagementOption() public void Enumerable_ShouldContain_ManagementQueryPasswordsOption() { // Arrange - var subject = new ManagementArguments(_config, 55); + ManagementArguments subject = new(_config, 55); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain("--management-query-passwords"); @@ -83,10 +84,10 @@ public void Enumerable_ShouldContain_ManagementQueryPasswordsOption() public void Enumerable_ShouldContain_ManagementHoldOption() { // Arrange - var subject = new ManagementArguments(_config, 66); + ManagementArguments subject = new(_config, 66); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--management-hold"); diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/NoBindArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/NoBindArgumentsTest.cs similarity index 83% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/NoBindArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/NoBindArgumentsTest.cs index 5e09b52c4..8fedb404e 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/NoBindArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/NoBindArgumentsTest.cs @@ -17,12 +17,13 @@ * along with ProtonVPN. If not, see . */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class NoBindArgumentsTest @@ -31,10 +32,10 @@ public class NoBindArgumentsTest public void Enumerable_ShouldContain_ExpectedNumberOfOptions() { // Arrange - var subject = new NoBindArguments(); + NoBindArguments subject = new(); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().HaveCount(1); @@ -44,10 +45,10 @@ public void Enumerable_ShouldContain_ExpectedNumberOfOptions() public void Enumerable_ShouldContain_NoBindOption() { // Arrange - var subject = new NoBindArguments(); + NoBindArguments subject = new(); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--nobind"); diff --git a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs similarity index 81% rename from test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs index ef84fa677..69a3df214 100644 --- a/test/ProtonVPN.Vpn.Test/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/OpenVpn/Arguments/TlsVerifyArgumentsTest.cs @@ -17,6 +17,7 @@ * along with ProtonVPN. If not, see . */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -24,7 +25,7 @@ using ProtonVPN.Common.Configuration; using ProtonVPN.Vpn.OpenVpn.Arguments; -namespace ProtonVPN.Vpn.Test.OpenVpn.Arguments +namespace ProtonVPN.Vpn.Tests.OpenVpn.Arguments { [TestClass] public class TlsVerifyArgumentsTest @@ -43,10 +44,10 @@ public void Enumerable_ShouldContain_ExpectedNumberOfOptions() // Arrange _config.TlsExportCertFolder = "ExportCert"; _config.TlsVerifyExePath = "ProtonVPN.TlsVerify.exe"; - var subject = new TlsVerifyArguments(_config, "nl-101.proton.com"); + TlsVerifyArguments subject = new TlsVerifyArguments(_config, "nl-101.proton.com"); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().HaveCount(3); @@ -60,10 +61,10 @@ public void Enumerable_ShouldContain_SetEnvOption() _config.TlsExportCertFolder = "ExportCert"; _config.TlsVerifyExePath = "ProtonVPN.TlsVerify.exe"; - var subject = new TlsVerifyArguments(_config, serverName); + TlsVerifyArguments subject = new TlsVerifyArguments(_config, serverName); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--setenv peer_dns_name \"{serverName}\""); @@ -77,10 +78,10 @@ public void Enumerable_ShouldContain_TlsExportCertOption() _config.TlsExportCertFolder = exportCertFolder; _config.TlsVerifyExePath = "ProtonVPN.TlsVerify.exe"; - var subject = new TlsVerifyArguments(_config, "gb-15.proton.com"); + TlsVerifyArguments subject = new TlsVerifyArguments(_config, "gb-15.proton.com"); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--tls-export-cert \"{exportCertFolder}\""); @@ -94,10 +95,10 @@ public void Enumerable_ShouldContain_TlsVerifyOption() _config.TlsExportCertFolder = "ExportCert"; _config.TlsVerifyExePath = tlsVerifyExePath; - var subject = new TlsVerifyArguments(_config, "gb-15.proton.com"); + TlsVerifyArguments subject = new TlsVerifyArguments(_config, "gb-15.proton.com"); // Act - var result = subject.ToList(); + List result = subject.ToList(); // Assert result.Should().Contain($"--tls-verify \"{tlsVerifyExePath}\""); diff --git a/test/ProtonVPN.Vpn.Test/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs similarity index 98% rename from test/ProtonVPN.Vpn.Test/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs index 7ede77f42..556723645 100644 --- a/test/ProtonVPN.Vpn.Test/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/PortMapping/Serializers/Common/MessageSerializerFactoryTest.cs @@ -24,7 +24,7 @@ using ProtonVPN.Vpn.PortMapping.Serializers; using ProtonVPN.Vpn.PortMapping.Serializers.Common; -namespace ProtonVPN.Vpn.Test.PortMapping.Serializers.Common +namespace ProtonVPN.Vpn.Tests.PortMapping.Serializers.Common { [TestClass] public class MessageSerializerFactoryTest diff --git a/test/ProtonVPN.Vpn.Test/Properties/AssemblyInfo.cs b/src/Tests/ProtonVPN.Vpn.Tests/Properties/AssemblyInfo.cs similarity index 92% rename from test/ProtonVPN.Vpn.Test/Properties/AssemblyInfo.cs rename to src/Tests/ProtonVPN.Vpn.Tests/Properties/AssemblyInfo.cs index 05205f064..deb70bcbc 100644 --- a/test/ProtonVPN.Vpn.Test/Properties/AssemblyInfo.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/Properties/AssemblyInfo.cs @@ -20,11 +20,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ProtonVPN.Vpn.Test")] +[assembly: AssemblyTitle("ProtonVPN.Vpn.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ProtonVPN.Vpn.Test")] +[assembly: AssemblyProduct("ProtonVPN.Vpn.Tests")] [assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Tests/ProtonVPN.Vpn.Tests/ProtonVPN.Vpn.Tests.csproj b/src/Tests/ProtonVPN.Vpn.Tests/ProtonVPN.Vpn.Tests.csproj new file mode 100644 index 000000000..bb5989017 --- /dev/null +++ b/src/Tests/ProtonVPN.Vpn.Tests/ProtonVPN.Vpn.Tests.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + {A16637C2-2D91-4953-AE04-D91EC188DD7B} + Library + Properties + ProtonVPN.Vpn.Tests + ProtonVPN.Vpn.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {03B8E43C-5680-4803-A745-0A104FE6620C} + ProtonVPN.Common + + + {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} + ProtonVPN.Crypto + + + {4aa7ce6f-7154-49c1-b598-46055d590cad} + ProtonVPN.Vpn + + + + + 5.8.0 + + + 2.1.2 + + + 2.1.2 + + + 4.3.0 + + + 4.3.4 + + + 4.3.1 + + + 5.0.0 + + + 4.5.4 + + + + + \ No newline at end of file diff --git a/test/ProtonVPN.Vpn.Test/WireGuard/WireGuardConnectionTest.cs b/src/Tests/ProtonVPN.Vpn.Tests/WireGuard/WireGuardConnectionTest.cs similarity index 98% rename from test/ProtonVPN.Vpn.Test/WireGuard/WireGuardConnectionTest.cs rename to src/Tests/ProtonVPN.Vpn.Tests/WireGuard/WireGuardConnectionTest.cs index 0a11d2381..b8a7abc50 100644 --- a/test/ProtonVPN.Vpn.Test/WireGuard/WireGuardConnectionTest.cs +++ b/src/Tests/ProtonVPN.Vpn.Tests/WireGuard/WireGuardConnectionTest.cs @@ -30,7 +30,7 @@ using ProtonVPN.Vpn.Gateways; using ProtonVPN.Vpn.WireGuard; -namespace ProtonVPN.Vpn.Test.WireGuard +namespace ProtonVPN.Vpn.Tests.WireGuard { [TestClass] public class WireGuardConnectionTest diff --git a/src/Tests/ProtonVPN.Vpn.Tests/app.config b/src/Tests/ProtonVPN.Vpn.Tests/app.config new file mode 100644 index 000000000..f10b4ac05 --- /dev/null +++ b/src/Tests/ProtonVPN.Vpn.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/TestTools.ApiClient/Api.cs b/src/Tests/TestTools.ApiClient/Api.cs similarity index 88% rename from test/TestTools.ApiClient/Api.cs rename to src/Tests/TestTools.ApiClient/Api.cs index a3e67f299..5e7eb7017 100644 --- a/test/TestTools.ApiClient/Api.cs +++ b/src/Tests/TestTools.ApiClient/Api.cs @@ -27,7 +27,6 @@ using ProtonVPN.Api.Contracts.Profiles; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Logging; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Auth; using ProtonVPN.Core.Settings; @@ -45,7 +44,7 @@ public Api(string username, string password) _username = username; _password = password; - TokenStorage tokenStorage = new(); + IAppSettings appSettings = Substitute.For(); Config config = new Config { ApiVersion = "3" }; IUserStorage userStorage = Substitute.For(); ILogger logger = Substitute.For(); @@ -56,9 +55,9 @@ public Api(string username, string password) _api = new Client(config, logger, new HttpClient { BaseAddress = new Uri("https://api.protonvpn.ch") - }, tokenStorage, appLanguageCache); + }, appSettings, appLanguageCache); - _auth = new UserAuth(_api, null, userStorage, tokenStorage, authCertificateManager); + _auth = new UserAuth(_api, null, userStorage, appSettings, authCertificateManager); } public async Task Login() @@ -98,11 +97,4 @@ private SecureString ToSecureString(string value) return result; } } - - internal class TokenStorage : ITokenStorage - { - public string AccessToken { get; set; } - public string RefreshToken { get; set; } - public string Uid { get; set; } - } } \ No newline at end of file diff --git a/test/TestTools.ApiClient/ApiVersion.cs b/src/Tests/TestTools.ApiClient/ApiVersion.cs similarity index 100% rename from test/TestTools.ApiClient/ApiVersion.cs rename to src/Tests/TestTools.ApiClient/ApiVersion.cs diff --git a/test/TestTools.ApiClient/Client.cs b/src/Tests/TestTools.ApiClient/Client.cs similarity index 98% rename from test/TestTools.ApiClient/Client.cs rename to src/Tests/TestTools.ApiClient/Client.cs index 9b38b08c7..6df755e39 100644 --- a/test/TestTools.ApiClient/Client.cs +++ b/src/Tests/TestTools.ApiClient/Client.cs @@ -37,7 +37,6 @@ using ProtonVPN.Api.Contracts.VpnSessions; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Logging; -using ProtonVPN.Core.Abstract; using ProtonVPN.Core.Settings; namespace TestTools.ApiClient @@ -50,9 +49,9 @@ public Client( Config config, ILogger logger, HttpClient client, - ITokenStorage tokenStorage, + IAppSettings appSettings, IAppLanguageCache appLanguageCache) - : base(logger, new ApiAppVersion(), tokenStorage, appLanguageCache, config) + : base(logger, new ApiAppVersion(), appSettings, appLanguageCache, config) { _client = client; } diff --git a/test/TestTools.ApiClient/Properties/AssemblyInfo.cs b/src/Tests/TestTools.ApiClient/Properties/AssemblyInfo.cs similarity index 100% rename from test/TestTools.ApiClient/Properties/AssemblyInfo.cs rename to src/Tests/TestTools.ApiClient/Properties/AssemblyInfo.cs diff --git a/test/TestTools.ApiClient/TestTools.ApiClient.csproj b/src/Tests/TestTools.ApiClient/TestTools.ApiClient.csproj similarity index 65% rename from test/TestTools.ApiClient/TestTools.ApiClient.csproj rename to src/Tests/TestTools.ApiClient/TestTools.ApiClient.csproj index 52f479a39..ec32584b5 100644 --- a/test/TestTools.ApiClient/TestTools.ApiClient.csproj +++ b/src/Tests/TestTools.ApiClient/TestTools.ApiClient.csproj @@ -18,7 +18,7 @@ true full false - bin\Debug\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -27,28 +27,16 @@ pdbonly true - bin\Release\ + ..\..\bin\ TRACE prompt 4 latest - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - @@ -64,25 +52,35 @@ - - + {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} ProtonVPN.Api.Contracts - + {3E905528-D87C-4552-A32D-66BF90D14DB0} ProtonVPN.Api - + {03B8E43C-5680-4803-A745-0A104FE6620C} ProtonVPN.Common - + {CA44B51D-7645-413A-818F-2C5B57DB67DD} ProtonVPN.Core + + + 4.3.0 + + + 5.0.0 + + + 4.5.4 + + \ No newline at end of file diff --git a/test/ProtonVPN.App.Test/app.config b/src/Tests/TestTools.ApiClient/app.config similarity index 72% rename from test/ProtonVPN.App.Test/app.config rename to src/Tests/TestTools.ApiClient/app.config index b551a3004..ba0ac79da 100644 --- a/test/ProtonVPN.App.Test/app.config +++ b/src/Tests/TestTools.ApiClient/app.config @@ -10,10 +10,6 @@ - - - - \ No newline at end of file diff --git a/test/TestTools.ProfileCleaner/Program.cs b/src/Tests/TestTools.ProfileCleaner/Program.cs similarity index 100% rename from test/TestTools.ProfileCleaner/Program.cs rename to src/Tests/TestTools.ProfileCleaner/Program.cs diff --git a/test/TestTools.ProfileCleaner/Properties/AssemblyInfo.cs b/src/Tests/TestTools.ProfileCleaner/Properties/AssemblyInfo.cs similarity index 100% rename from test/TestTools.ProfileCleaner/Properties/AssemblyInfo.cs rename to src/Tests/TestTools.ProfileCleaner/Properties/AssemblyInfo.cs diff --git a/test/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj b/src/Tests/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj similarity index 96% rename from test/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj rename to src/Tests/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj index dbe8660ac..648292ee4 100644 --- a/test/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj +++ b/src/Tests/TestTools.ProfileCleaner/TestTools.ProfileCleaner.csproj @@ -18,7 +18,7 @@ true full false - ..\..\src\bin\ + ..\..\bin\ DEBUG;TRACE prompt 4 @@ -29,7 +29,7 @@ AnyCPU pdbonly true - ..\..\src\bin\ + ..\..\bin\ TRACE prompt 4 diff --git a/test/ProtonVPN.Service.Test/app.config b/src/Tests/TestTools.ProfileCleaner/app.config similarity index 71% rename from test/ProtonVPN.Service.Test/app.config rename to src/Tests/TestTools.ProfileCleaner/app.config index 3df9e48cd..ba0ac79da 100644 --- a/test/ProtonVPN.Service.Test/app.config +++ b/src/Tests/TestTools.ProfileCleaner/app.config @@ -7,8 +7,8 @@ - - + + diff --git a/test/ProtonVPN.App.Test/packages.config b/test/ProtonVPN.App.Test/packages.config deleted file mode 100644 index f4dfca849..000000000 --- a/test/ProtonVPN.App.Test/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/ProtonVPN.Common.Test.csproj b/test/ProtonVPN.Common.Test/ProtonVPN.Common.Test.csproj deleted file mode 100644 index 31e23c05c..000000000 --- a/test/ProtonVPN.Common.Test/ProtonVPN.Common.Test.csproj +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - Debug - AnyCPU - {5F2931B6-9A77-4F94-80CD-BC9B9A0C64BF} - Library - Properties - ProtonVPN.Common.Test - ProtonVPN.Common.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - ..\..\packages\Polly.7.2.0\lib\net472\Polly.dll - - - - - - - - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - ..\..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll - True - True - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - PreserveNewest - - - - - {03b8e43c-5680-4803-a745-0a104fe6620c} - ProtonVPN.Common - - - {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} - ProtonVPN.Crypto - - - {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Common.Test/packages.config b/test/ProtonVPN.Common.Test/packages.config deleted file mode 100644 index 41419616a..000000000 --- a/test/ProtonVPN.Common.Test/packages.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Core.Test/OS/Net/DoH/MainHostnameTest.cs b/test/ProtonVPN.Core.Test/OS/Net/DoH/MainHostnameTest.cs deleted file mode 100644 index 6c8a227b4..000000000 --- a/test/ProtonVPN.Core.Test/OS/Net/DoH/MainHostnameTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 Proton Technologies AG - * - * This file is part of ProtonVPN. - * - * ProtonVPN 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. - * - * ProtonVPN 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 ProtonVPN. If not, see . - */ - -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.Core.OS.Net.DoH; - -namespace ProtonVPN.Core.Test.OS.Net.DoH -{ - [TestClass] - public class MainHostnameTest - { - [TestMethod] - public void Value_ShouldReturnCorrectHost() - { - new MainHostname("https://api.protonvpn.ch") - .Value(string.Empty) - .Should() - .Be("dMFYGSLTQOJXXI33OOZYG4LTDNA.protonpro.xyz"); - } - - [TestMethod] - public void Value_ShouldReturnCorrectHostWithUid() - { - string uid = "test-uid"; - new MainHostname("https://api.protonvpn.ch") - .Value(uid) - .Should() - .Be($"{uid}.dMFYGSLTQOJXXI33OOZYG4LTDNA.protonpro.xyz"); - } - } -} diff --git a/test/ProtonVPN.Core.Test/ProtonVPN.Core.Test.csproj b/test/ProtonVPN.Core.Test/ProtonVPN.Core.Test.csproj deleted file mode 100644 index 8accf71f9..000000000 --- a/test/ProtonVPN.Core.Test/ProtonVPN.Core.Test.csproj +++ /dev/null @@ -1,207 +0,0 @@ - - - - - Debug - AnyCPU - {FA0D86B4-2B86-4DFE-B7E6-7C809DB74A13} - Library - Properties - ProtonVPN.Core.Test - ProtonVPN.Core.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\Caliburn.Micro.Core.3.2.0\lib\net45\Caliburn.Micro.dll - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\DnsClient.1.5.0\lib\net471\DnsClient.dll - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - ..\..\packages\Polly.7.2.0\lib\net472\Polly.dll - - - - ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - - - - - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - - ..\..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll - True - True - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - - ..\..\packages\System.Runtime.Serialization.Primitives.4.3.0\lib\net46\System.Runtime.Serialization.Primitives.dll - True - True - - - ..\..\packages\System.Security.AccessControl.5.0.0\lib\net461\System.Security.AccessControl.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {9E4D6072-C8DE-475A-B9A7-4B6BF6EEEAEB} - ProtonVPN.Api.Contracts - - - {3E905528-D87C-4552-A32D-66BF90D14DB0} - ProtonVPN.Api - - - {0cdca012-bb2d-49b3-944e-ce80d75d651a} - ProtonVPN.App - - - {03b8e43c-5680-4803-a745-0a104fe6620c} - ProtonVPN.Common - - - {ca44b51d-7645-413a-818f-2c5b57db67dd} - ProtonVPN.Core - - - {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} - ProtonVPN.Crypto - - - {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Core.Test/packages.config b/test/ProtonVPN.Core.Test/packages.config deleted file mode 100644 index 749e00b8c..000000000 --- a/test/ProtonVPN.Core.Test/packages.config +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Crypto.Test/packages.config b/test/ProtonVPN.Crypto.Test/packages.config deleted file mode 100644 index f84cb1064..000000000 --- a/test/ProtonVPN.Crypto.Test/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Service.Contract.Test/ProtonVPN.Service.Contract.Test.csproj b/test/ProtonVPN.Service.Contract.Test/ProtonVPN.Service.Contract.Test.csproj deleted file mode 100644 index a325bbec7..000000000 --- a/test/ProtonVPN.Service.Contract.Test/ProtonVPN.Service.Contract.Test.csproj +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - Debug - AnyCPU - {C4E97B08-345A-423B-BD4C-76593D1401B6} - Library - Properties - ProtonVPN.Service.Contract.Test - ProtonVPN.Service.Contract.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - - - - - - - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - ..\..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll - True - True - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - {03B8E43C-5680-4803-A745-0A104FE6620C} - ProtonVPN.Common - - - {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} - ProtonVPN.Crypto - - - {96c5d688-c0f1-4a63-9e26-e485fd0e1365} - ProtonVPN.Service.Contract - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Service.Contract.Test/packages.config b/test/ProtonVPN.Service.Contract.Test/packages.config deleted file mode 100644 index 8ec1b2206..000000000 --- a/test/ProtonVPN.Service.Contract.Test/packages.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Service.Test/ProtonVPN.Service.Test.csproj b/test/ProtonVPN.Service.Test/ProtonVPN.Service.Test.csproj deleted file mode 100644 index b99da14ef..000000000 --- a/test/ProtonVPN.Service.Test/ProtonVPN.Service.Test.csproj +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - Debug - AnyCPU - {4290C007-2142-4AD1-8EB6-F80EF2F45AA4} - Library - Properties - ProtonVPN.Service.Test - ProtonVPN.Service.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - - - - - - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - ..\..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll - True - True - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - {03B8E43C-5680-4803-A745-0A104FE6620C} - ProtonVPN.Common - - - {1cf1b8bf-57eb-4e49-b644-0a8f2dfeeb58} - ProtonVPN.NetworkFilter - - - {96C5D688-C0F1-4A63-9E26-E485FD0E1365} - ProtonVPN.Service.Contract - - - {25781B52-5858-4387-80A5-C9C38C32B3CC} - ProtonVPN.Service - - - {4aa7ce6f-7154-49c1-b598-46055d590cad} - ProtonVPN.Vpn - - - {A0DA4200-6643-4F2C-8450-65B8CE8A5576} - ProtonVPN.Test.Common - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Service.Test/packages.config b/test/ProtonVPN.Service.Test/packages.config deleted file mode 100644 index 08c835d3e..000000000 --- a/test/ProtonVPN.Service.Test/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.UI.Test/ProtonVPN.UI.Test.csproj b/test/ProtonVPN.UI.Test/ProtonVPN.UI.Test.csproj deleted file mode 100644 index 8ca4b6aa8..000000000 --- a/test/ProtonVPN.UI.Test/ProtonVPN.UI.Test.csproj +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - Debug - AnyCPU - {24E940FF-C9F3-4D5C-8FCF-CA527F055318} - Library - Properties - ProtonVPN.UI.Test - ProtonVPN.UI.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - x64 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - - ..\..\packages\Appium.WebDriver.4.1.1\lib\net45\Appium.Net.dll - - - ..\..\packages\Autofac.4.9.4\lib\net45\Autofac.dll - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\FlaUI.Core.3.2.0\lib\net45\FlaUI.Core.dll - - - ..\..\packages\FlaUI.UIA3.3.2.0\lib\net45\FlaUI.UIA3.dll - - - ..\..\packages\Interop.UIAutomationClient.10.18362.0\lib\net45\Interop.UIAutomationClient.dll - False - - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - ..\..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll - - - - - ..\..\packages\DotNetSeleniumExtras.PageObjects.3.11.0\lib\net45\SeleniumExtras.PageObjects.dll - - - ..\..\packages\DotNetSeleniumExtras.WaitHelpers.3.11.0\lib\net45\SeleniumExtras.WaitHelpers.dll - - - - - - - - - - - - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - ..\..\packages\TestRail.3.2.1\lib\net461\TestRail.dll - - - ..\..\packages\Selenium.WebDriver.3.141.0\lib\net45\WebDriver.dll - - - ..\..\packages\Selenium.Support.3.141.0\lib\net45\WebDriver.Support.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {0CDCA012-BB2D-49B3-944E-CE80D75D651A} - ProtonVPN.App - - - {03B8E43C-5680-4803-A745-0A104FE6620C} - ProtonVPN.Common - - - {CA44B51D-7645-413A-818F-2C5B57DB67DD} - ProtonVPN.Core - - - {96C5D688-C0F1-4A63-9E26-E485FD0E1365} - ProtonVPN.Service.Contract - - - {F059E362-20A2-472B-82CA-E727D31AC0C7} - TestTools.ApiClient - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.UI.Test/packages.config b/test/ProtonVPN.UI.Test/packages.config deleted file mode 100644 index d7ac888ee..000000000 --- a/test/ProtonVPN.UI.Test/packages.config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Update.Test/Contracts/FileContractTest.cs b/test/ProtonVPN.Update.Test/Contracts/FileContractTest.cs deleted file mode 100644 index c04bb8e46..000000000 --- a/test/ProtonVPN.Update.Test/Contracts/FileContractTest.cs +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2022 Proton Technologies AG - * - * This file is part of ProtonVPN. - * - * ProtonVPN 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. - * - * ProtonVPN 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 ProtonVPN. If not, see . - */ - -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using ProtonVPN.Update.Contracts; - -namespace ProtonVPN.Update.Test.Contracts -{ - [TestClass] - public class FileContractTest - { - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(null); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeTrue_WhenOther_IsSelf() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb"}; - - var result = file.Equals(file); - - result.Should().BeTrue(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_IsNotFileContract() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(new { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeTrue_WhenOther_IsEqual() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeTrue(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Url_IsDifferent() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "http://ubiquito.com/abc.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Url_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenThis_Url_IsNull() - { - var file = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeTrue_WhenBoth_Urls_AreNull() - { - var file = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = null, Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeTrue(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Sha512CheckSum_IsDifferent() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "abcdefghij", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Sha512CheckSum_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = null, Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenThis_Sha512CheckSum_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = null, Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeTrue_WhenBoth_Sha512CheckSum_AreNull() - { - var file = new FileContract { Url = null, Sha512CheckSum = null, Arguments = "/qb" }; - var other = new FileContract { Url = null, Sha512CheckSum = null, Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeTrue(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Arguments_IsDifferent() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenOther_Arguments_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeFalse_WhenThis_Arguments_IsNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = "/qb" }; - - var result = file.Equals(other); - - result.Should().BeFalse(); - } - - [TestMethod] - public void Equals_ShouldBeTrue_WhenBoth_Arguments_AreNull() - { - var file = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; - var other = new FileContract { Url = "https://protonvpn.com/download.exe", Sha512CheckSum = "123456789", Arguments = null }; - - var result = file.Equals(other); - - result.Should().BeTrue(); - } - } -} diff --git a/test/ProtonVPN.Vpn.Test/ProtonVPN.Vpn.Test.csproj b/test/ProtonVPN.Vpn.Test/ProtonVPN.Vpn.Test.csproj deleted file mode 100644 index 812b539a1..000000000 --- a/test/ProtonVPN.Vpn.Test/ProtonVPN.Vpn.Test.csproj +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - Debug - AnyCPU - {A16637C2-2D91-4953-AE04-D91EC188DD7B} - Library - Properties - ProtonVPN.Vpn.Test - ProtonVPN.Vpn.Test - v4.7.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - ..\..\src\bin\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - ..\..\src\bin\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - - ..\..\packages\FluentAssertions.5.8.0\lib\net47\FluentAssertions.dll - - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\NSubstitute.4.3.0\lib\net46\NSubstitute.dll - - - - - - - - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll - True - True - - - ..\..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll - True - True - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - - - - - - - - {03B8E43C-5680-4803-A745-0A104FE6620C} - ProtonVPN.Common - - - {BA2D505E-CED3-4FCB-A463-DAF6B77C18DE} - ProtonVPN.Crypto - - - {4aa7ce6f-7154-49c1-b598-46055d590cad} - ProtonVPN.Vpn - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/ProtonVPN.Vpn.Test/packages.config b/test/ProtonVPN.Vpn.Test/packages.config deleted file mode 100644 index 08c835d3e..000000000 --- a/test/ProtonVPN.Vpn.Test/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/TestTools.ApiClient/app.config b/test/TestTools.ApiClient/app.config deleted file mode 100644 index 6f9a084c5..000000000 --- a/test/TestTools.ApiClient/app.config +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/TestTools.ApiClient/packages.config b/test/TestTools.ApiClient/packages.config deleted file mode 100644 index 127f88b1f..000000000 --- a/test/TestTools.ApiClient/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file