Adding Live backend tests

These tests are not unit tests but actual backup tests to backends (both cloud and self hosted containers).
This commit is contained in:
Marcelo Ceccon
2024-10-30 09:09:17 +01:00
parent 760c2902e4
commit 1189bd2fb1
17 changed files with 1320 additions and 0 deletions
+289
View File
@@ -0,0 +1,289 @@
name: Backend Live Tests
'on':
- push
- pull_request
jobs:
check_secrets:
runs-on: ubuntu-latest
outputs:
s3_secrets_available: '${{ steps.check_s3.outputs.available }}'
dropbox_secrets_available: '${{ steps.check_dropbox.outputs.available }}'
azure_secrets_available: '${{ steps.check_azure.outputs.available }}'
steps:
- id: check_s3
name: Check S3 secrets
shell: bash
run: |
echo "Starting S3 secrets check..."
if [[ -n "${{ secrets.TESTCREDENTIAL_S3_BUCKETNAME }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_S3_KEY }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_S3_REGION }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_S3_SECRET }}" ]]; then
echo "All S3 secrets found"
echo "available=true" >> $GITHUB_OUTPUT
else
echo "Missing some S3 secrets"
echo "available=false" >> $GITHUB_OUTPUT
fi
echo "S3 check completed"
- id: check_dropbox
name: Check Dropbox secrets
shell: bash
run: |
echo "Starting Dropbox secrets check..."
if [[ -n "${{ secrets.TESTCREDENTIAL_DROPBOX_FOLDER }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_DROPBOX_TOKEN }}" ]]; then
echo "All Dropbox secrets found"
echo "available=true" >> $GITHUB_OUTPUT
else
echo "Missing some Dropbox secrets"
echo "available=false" >> $GITHUB_OUTPUT
fi
echo "Dropbox check completed"
- id: check_azure
name: Check Azure secrets
shell: bash
run: |
echo "Starting Azure secrets check..."
if [[ -n "${{ secrets.TESTCREDENTIAL_AZURE_ACCOUNTNAME }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_AZURE_ACCESSKEY }}" ]] && \
[[ -n "${{ secrets.TESTCREDENTIAL_AZURE_CONTAINERNAME }}" ]]; then
echo "All Azure secrets found"
echo "available=true" >> $GITHUB_OUTPUT
else
echo "Missing some Azure secrets"
echo "available=false" >> $GITHUB_OUTPUT
fi
echo "Azure check completed"
- name: Debug Output
shell: bash
run: |
echo "S3 Available: ${{ steps.check_s3.outputs.available }}"
echo "Dropbox Available: ${{ steps.check_dropbox.outputs.available }}"
test_ftp:
name: FTP Tests
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
max-parallel: 1
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Setup Testcontainers Cloud Client
uses: atomicjar/testcontainers-cloud-setup-action@v1
with:
token: '${{ secrets.TC_CLOUD_TOKEN }}'
- name: Run FTP tests
env:
TC_CLOUD_TOKEN: '${{ secrets.TC_CLOUD_TOKEN }}'
run: >-
dotnet test --no-build --filter="ClassName~FtpTests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
test_s3:
needs: check_secrets
if: needs.check_secrets.outputs.s3_secrets_available == 'true'
name: S3 Tests
runs-on: '${{ matrix.os }}'
strategy:
max-parallel: 1
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Run S3 tests
env:
TC_CLOUD_TOKEN: '${{ secrets.TC_CLOUD_TOKEN }}'
TESTCREDENTIAL_S3_BUCKETNAME: '${{ secrets.TESTCREDENTIAL_S3_BUCKETNAME }}'
TESTCREDENTIAL_S3_KEY: '${{ secrets.TESTCREDENTIAL_S3_KEY }}'
TESTCREDENTIAL_S3_REGION: '${{ secrets.TESTCREDENTIAL_S3_REGION }}'
TESTCREDENTIAL_S3_SECRET: '${{ secrets.TESTCREDENTIAL_S3_SECRET }}'
run: >-
dotnet test --no-build --filter="ClassName~S3Tests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
test_azure:
needs: check_secrets
if: needs.check_secrets.outputs.azure_secrets_available == 'true'
name: Azure Tests
runs-on: '${{ matrix.os }}'
strategy:
max-parallel: 1
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Run Azure tests
env:
TESTCREDENTIAL_AZURE_ACCOUNTNAME: '${{ secrets.TESTCREDENTIAL_AZURE_ACCOUNTNAME }}'
TESTCREDENTIAL_AZURE_ACCESSKEY: '${{ secrets.TESTCREDENTIAL_AZURE_ACCESSKEY }}'
TESTCREDENTIAL_AZURE_CONTAINERNAME: '${{ secrets.TESTCREDENTIAL_AZURE_CONTAINERNAME }}'
run: >-
dotnet test --no-build --filter="ClassName~AzureTests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
test_webdav:
name: Webdav Tests
runs-on: '${{ matrix.os }}'
strategy:
max-parallel: 1
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Setup Testcontainers Cloud Client
uses: atomicjar/testcontainers-cloud-setup-action@v1
with:
token: '${{ secrets.TC_CLOUD_TOKEN }}'
- name: Run Webdav tests
env:
TC_CLOUD_TOKEN: '${{ secrets.TC_CLOUD_TOKEN }}'
run: >-
dotnet test --no-build --filter="ClassName~WebDavTests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
test_dropbox:
needs: check_secrets
if: needs.check_secrets.outputs.dropbox_secrets_available == 'true'
name: DropBox Tests
runs-on: '${{ matrix.os }}'
strategy:
max-parallel: 1
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Run Dropbox tests
env:
TC_CLOUD_TOKEN: '${{ secrets.TC_CLOUD_TOKEN }}'
TESTCREDENTIAL_DROPBOX_FOLDER: '${{ secrets.TESTCREDENTIAL_DROPBOX_FOLDER }}'
TESTCREDENTIAL_DROPBOX_TOKEN: '${{ secrets.TESTCREDENTIAL_DROPBOX_TOKEN }}'
run: >-
dotnet test --no-build --filter="ClassName~DropBoxTests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
test_ssh:
name: SSH Tests
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
max-parallel: 1
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Checkout source
uses: actions/checkout@v4
- name: Restore NuGet dependencies
run: >-
dotnet restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Build project
run: >-
dotnet build --no-restore
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
- name: Setup Testcontainers Cloud Client
uses: atomicjar/testcontainers-cloud-setup-action@v1
with:
token: '${{ secrets.TC_CLOUD_TOKEN }}'
- name: Run SSH tests
env:
TC_CLOUD_TOKEN: '${{ secrets.TC_CLOUD_TOKEN }}'
run: >-
dotnet test --no-build --filter="ClassName~SshTests"
--logger:"console;verbosity=detailed"
LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln
@@ -0,0 +1,50 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Azure;
/// <summary>
/// Tests for Dropbox backend
/// </summary>
[TestClass]
public sealed class AzureTests : BaseTest
{
/// <summary>
/// Basic Dropbox test. There are no adicional parameters to be set or tested.
/// </summary>
[TestMethod]
public Task TestAzureBlob()
{
CheckRequiredEnvironment(["TESTCREDENTIAL_AZURE_ACCOUNTNAME","TESTCREDENTIAL_AZURE_ACCESSKEY","TESTCREDENTIAL_AZURE_CONTAINERNAME"
]);
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"azure://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_CONTAINERNAME")}?auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_ACCOUNTNAME")}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_ACCESSKEY")!)}"
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
return Task.CompletedTask;
}
}
@@ -0,0 +1,99 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Base;
/// <summary>
/// Base class for tests that require a running SFTPGo instance which will
/// be used to test the FTP, Webdav & SSH backends.
///
/// It handles the creation of the necessary configuration files and certificates
/// </summary>
public class BaseSftpgoTest : BaseTest
{
protected static readonly string TEST_USER_NAME = "testuser";
protected static readonly string TEST_USER_PASSWORD = "testpassword";
protected static readonly string CERTIFICATE_FILE = "fullchain.pem";
protected static readonly string CERTIFICATE_PRIVATE_KEY_FILE = "privkey.pem";
/// <summary>
/// Creates a josn file with the configuration for the SFTPGo instance
/// </summary>
/// <param name="directoryInfo">directory in which to create the file</param>
protected void CreateUsersFile(DirectoryInfo directoryInfo)
{
var sftpgoConfig = "{\"users\":[{\"id\":1,\"status\":1,\"username\":\"testuser\",\"password\":\"$2a$10$BTqu.odQz6jLQAi70KbQJOYBcRHpbhP4uxqcfFXZalq4zUsQZGj/6\",\"has_password\":true,\"home_dir\":\"/srv/sftpgo/data/testuser\",\"uid\":0,\"gid\":0,\"max_sessions\":0,\"quota_size\":0,\"quota_files\":0,\"permissions\":{\"/\":[\"*\"]},\"upload_data_transfer\":0,\"download_data_transfer\":0,\"total_data_transfer\":0,\"created_at\":1729521839754,\"updated_at\":1729521839754,\"last_password_change\":1729521839754,\"filters\":{\"hooks\":{\"external_auth_disabled\":false,\"pre_login_disabled\":false,\"check_password_disabled\":false},\"totp_config\":{\"secret\":{}}},\"filesystem\":{\"provider\":0,\"osconfig\":{},\"s3config\":{\"access_secret\":{}},\"gcsconfig\":{\"credentials\":{}},\"azblobconfig\":{\"account_key\":{},\"sas_url\":{}},\"cryptconfig\":{\"passphrase\":{}},\"sftpconfig\":{\"password\":{},\"private_key\":{},\"key_passphrase\":{}},\"httpconfig\":{\"password\":{},\"api_key\":{}}}}],\"groups\":[],\"folders\":[],\"admins\":[{\"id\":1,\"status\":1,\"username\":\"admin\",\"password\":\"$2a$10$HJGY10EuEPPajgut3OMTte5NXqbn1RXMTzy8BUA639a6qzinOjIx6\",\"permissions\":[\"*\"],\"filters\":{\"require_two_factor\":false,\"totp_config\":{\"secret\":{}},\"preferences\":{}},\"created_at\":1729521817148,\"updated_at\":1729521817148,\"last_login\":1729521817152}],\"api_keys\":[],\"shares\":[],\"event_actions\":[],\"event_rules\":[],\"roles\":[],\"ip_lists\":[],\"configs\":{},\"version\":16}";
using var usersFile = File.Create(Path.Combine(directoryInfo.FullName, "users.json"));
using var userFileWritter = new StreamWriter(usersFile);
userFileWritter.WriteLine(sftpgoConfig);
}
/// <summary>
/// Create a self signed certificate to be used on WebDav and SFTP connections.
///
/// Its created on a temporary directory and the private key and public key are saved in PEM format.
///
/// It is up to the caller to delete the files albeit its in the OS temporary directory.
/// </summary>
/// <returns>Returns the directory in which certificate was created</returns>
protected DirectoryInfo CreateHttpsCertificates()
{
using var rsa = RSA.Create(2048);
var request = new CertificateRequest(
$"CN=TestCertificateForWebdav",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature,
false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
[new Oid("1.3.6.1.5.5.7.3.1")], false));
var certificate = request.CreateSelfSigned(
DateTimeOffset.Now.AddDays(-1),
DateTimeOffset.Now.AddDays(30));
if (OperatingSystem.IsWindows()) certificate.FriendlyName = "Test Certificate";
var directoryInfo = Directory.CreateTempSubdirectory("TemporaryCertificates");
using var privateKeyFile = File.Create(Path.Combine(directoryInfo.FullName, CERTIFICATE_PRIVATE_KEY_FILE));
using var publicKeyFile = File.Create(Path.Combine(directoryInfo.FullName, CERTIFICATE_FILE));
using var privateKeyWriter = new StreamWriter(privateKeyFile);
using var publicKeyWriter = new StreamWriter(publicKeyFile);
privateKeyWriter.WriteLine(certificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem());
publicKeyWriter.WriteLine(certificate.ExportCertificatePem());
return directoryInfo;
}
}
@@ -0,0 +1,18 @@
namespace Duplicati.Backend.Tests.Base;
public class BaseTest
{
/// <summary>
/// Checks if the environment variables are set and if not fails with detailed about the missing variables
/// </summary>
/// <param name="requiredEnvironmentVariables">Array of variables to be checked</param>
protected void CheckRequiredEnvironment(string[] requiredEnvironmentVariables)
{
var missingVariables = requiredEnvironmentVariables
.Where(variable => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(variable)))
.ToList();
if (missingVariables.Any()) Assert.Fail($"Required environment variables not set: {string.Join(", ", missingVariables)}");
}
}
@@ -0,0 +1,52 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Base;
/// <summary>
/// Tests to check if the TestContainers is available
/// </summary>
[TestClass]
public class PreliminaryTests
{
[TestMethod]
public async Task TestContainersEngineReady()
{
using var dockerClientConfiguration =
TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(ResourceReaper
.DefaultSessionId);
using var dockerClient = dockerClientConfiguration.CreateClient();
var versionResponse = await dockerClient.System.GetSystemInfoAsync()
.ConfigureAwait(false);
if (!(versionResponse.ServerVersion.Contains("Testcontainers Desktop") ||
versionResponse.ServerVersion.Contains("testcontainers cloud")))
{
Assert.Fail("Testcontainers engine not found");
}
Console.WriteLine($"Engine configured: {versionResponse.ServerVersion}");
}
}
@@ -0,0 +1,49 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Dropbox;
/// <summary>
/// Tests for Dropbox backend
/// </summary>
[TestClass]
public sealed class DropBoxTests : BaseTest
{
/// <summary>
/// Basic Dropbox test. There are no adicional parameters to be set or tested.
/// </summary>
[TestMethod]
public Task TestDropBox()
{
CheckRequiredEnvironment(["TESTCREDENTIAL_DROPBOX_FOLDER","TESTCREDENTIAL_DROPBOX_TOKEN"]);
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"dropbox://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_DROPBOX_FOLDER")}/?authid={Environment.GetEnvironmentVariable("TESTCREDENTIAL_DROPBOX_TOKEN")}"
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
return Task.CompletedTask;
}
}
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>Commandline implementation of Duplicati</Description>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10"/>
<PackageReference Include="MSTest.TestFramework" Version="2.2.10"/>
<PackageReference Include="Testcontainers" Version="3.4.0"/>
<PackageReference Include="TestContainers.Container.Abstractions" Version="1.5.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Backend.Tests", "Duplicati.Backend.Tests.csproj", "{6D888116-3424-4DE4-8AE3-D01A44A3EE38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTester", "..\..\Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{41756E86-799B-4142-9948-D9D94054BA15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Release|Any CPU.Build.0 = Release|Any CPU
{41756E86-799B-4142-9948-D9D94054BA15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41756E86-799B-4142-9948-D9D94054BA15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41756E86-799B-4142-9948-D9D94054BA15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41756E86-799B-4142-9948-D9D94054BA15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -0,0 +1,156 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.FTP;
/// <summary>
/// FTP Tests
/// </summary>
[TestClass]
public sealed class FtpTests : BaseSftpgoTest
{
/// <summary>
/// Test a simple FTP connection, no SSL
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task TestSimpleFtp()
{
var outputConsumer = new OutputConsumer();
var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute |
UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute |
UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute;
var temporaryKeysDir = CreateHttpsCertificates();
CreateUsersFile(temporaryKeysDir);
var container = new ContainerBuilder()
.WithImage("drakkan/sftpgo")
.WithEnvironment("SFTPGO_LOG_LEVEL", "debug")
.WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json")
.WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__PORT", "2121")
.WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__END", "21000")
.WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__START", "20000")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY", "1")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_FILE", CERTIFICATE_FILE)
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_KEY_FILE", CERTIFICATE_PRIVATE_KEY_FILE)
.WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions)
.WithPortBinding(2121, 2121)
.WithOutputConsumer(outputConsumer);
container = Enumerable.Range(20000, 1000)
.Aggregate(container, (current, port) => current.WithPortBinding(port, port));
var builtContainer = container
.WithOutputConsumer(outputConsumer)
.Build();
Console.WriteLine("Starting container");
await builtContainer.StartAsync();
// Once started we will already cleanup temporary directory
temporaryKeysDir.Delete(true);
Console.WriteLine("Waiting X seconds");
await Task.Delay(TimeSpan.FromSeconds(5));
// Running this ignoring the certificate
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"ftp://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:2121/",
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0)
{
Console.WriteLine(await outputConsumer.GetStreamsOutput());
Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
}
await builtContainer.StopAsync();
}
/// <summary>
/// Test FTP connection with SSL/TLS
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task TestFtpSsl()
{
var outputConsumer = new OutputConsumer();
var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute |
UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute |
UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute;
var temporaryKeysDir = CreateHttpsCertificates();
CreateUsersFile(temporaryKeysDir);
var container = new ContainerBuilder()
.WithImage("drakkan/sftpgo")
.WithEnvironment("SFTPGO_LOG_LEVEL", "debug")
.WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json")
.WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__PORT", "2121")
.WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__END", "23000")
.WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__START", "22000")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY", "1")
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_FILE", CERTIFICATE_FILE)
.WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_KEY_FILE", CERTIFICATE_PRIVATE_KEY_FILE)
.WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions)
.WithPortBinding(2122, 2121)
.WithOutputConsumer(outputConsumer);
container = Enumerable.Range(22000, 1000)
.Aggregate(container, (current, port) => current.WithPortBinding(port, port));
var builtContainer = container
.WithOutputConsumer(outputConsumer)
.Build();
Console.WriteLine("Starting container");
await builtContainer.StartAsync();
// Once started we will already cleanup temporary directory
temporaryKeysDir.Delete(true);
Console.WriteLine("Waiting X seconds");
await Task.Delay(TimeSpan.FromSeconds(5));
// Running this ignoring the certificate
var exitCode = CommandLine.BackendTester.Program.Main(
["ftp://testuser:testpassword@localhost:2122/", "use-ssl=true"]);
if (exitCode != 0)
{
Console.WriteLine(await outputConsumer.GetStreamsOutput());
Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
}
await builtContainer.StopAsync();
}
}
@@ -0,0 +1,30 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
global using System.Security.Cryptography;
global using System.Security.Cryptography.X509Certificates;
global using System.Text;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using Duplicati.Backend.Tests.Base;
global using Duplicati.Backend.Tests.Utility;
global using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -0,0 +1,50 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Google_Drive;
/// <summary>
/// Google Drive tests
/// </summary>
[TestClass]
public sealed class GoogleDriveTests : BaseTest
{
[TestMethod]
public Task TestGoogleDriveSimple()
{
CheckRequiredEnvironment(["TESTCREDENTIAL_GOOGLEDRIVE_TOKEN","TESTCREDENTIAL_GOOGLEDRIVE_FOLDER"]);
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"googledrive:///{Environment.GetEnvironmentVariable("TESTCREDENTIAL_GOOGLEDRIVE_FOLDER")}?authid={Environment.GetEnvironmentVariable("TESTCREDENTIAL_GOOGLEDRIVE_TOKEN")}",
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
return Task.CompletedTask;
}
}
@@ -0,0 +1,39 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests;
/// <summary>
/// Glboal parameters to be used on all tests
/// </summary>
public static class Parameters
{
// Standard parameters for the backend tester
public static string[] GlobalTestParameters =>
[
"--auto-clean",
"--auto-create-folder",
"--extended-chars=_-",
"--force",
$"--max-file-size={Environment.GetEnvironmentVariable("MAX_FILE_SIZE") ?? "1000kb"}",
$"--number-of-files={Environment.GetEnvironmentVariable("NUMBER_OF_FILES") ?? "20"}"
];
}
@@ -0,0 +1,76 @@
# Environment variables for tests
On github actions these are mapped 1:1 to secrets, even the non password fields are stored in secrets.
Tests like FTP, SSH and Webdav are self-contained, they do not require any environment variable.
## General
These control the size and number of files generated.
```
MAX_FILE_SIZE default is 1000kb
NUMBER_OF_FILES default is 20
```
## Google Drive:
Google Drive credentials are mapped to the following environment variables:
```
TESTCREDENTIAL_GOOGLEDRIVE_FOLDER
TESTCREDENTIAL_GOOGLEDRIVE_TOKEN
```
## S3
S3 credentials are mapped to the following environment variables:
Attention: **AWS TESTCREDENTIAL_S3_SECRET is URI escaped automatically, supply the raw value.**
```
TESTCREDENTIAL_S3_KEY
TESTCREDENTIAL_S3_SECRET
TESTCREDENTIAL_S3_BUCKETNAME
TESTCREDENTIAL_S3_REGION
```
## Dropbox
Dropbox credentials are mapped to the following environment variables:
```
TESTCREDENTIAL_DROPBOX_FOLDER
TESTCREDENTIAL_DROPBOX_TOKEN
```
## Azure Blob
Attention: **TESTCREDENTIAL_AZURE_ACCESSKEY is URI escaped automatically, supply the raw value.**
```
TESTCREDENTIAL_AZURE_ACCOUNTNAME
TESTCREDENTIAL_AZURE_ACCESSKEY
TESTCREDENTIAL_AZURE_CONTAINERNAME
```
## Running the tests
[TestContainers](https://testcontainers.org/) is a pre-requisite for SSH, FTP and Webdav tests. It is not required for the other tests.
Set the environment variables as described above, then run the tests using the following commands:
Minimal Verbosity:
`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=normal"`
Running with full verbosity (useful if tests are failing):
`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=detailed"`
Running specific tests:
`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=detailed" --filter="Name=TestDropBox"`
@@ -0,0 +1,76 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.S3;
/// <summary>
/// S3 Tests
/// </summary>
[TestClass]
public sealed class S3Tests : BaseTest
{
/// <summary>
/// Perform tests using AWS Client
/// </summary>
/// <returns></returns>
[TestMethod]
public Task TestS3WithAwsClient()
{
return TestS3WithClient("aws");
}
/// <summary>
/// Perform tests using Minio Client
/// </summary>
/// <returns></returns>
[TestMethod]
public Task TestS3WithMinioClient()
{
return TestS3WithClient("minio");
}
/// <summary>
/// Performs tests with selected client. The S3 tests don't have any additional parameters to be set or tested.
/// </summary>
/// <param name="client">client to be used, either aws or minio</param>
/// <returns></returns>
private Task TestS3WithClient(string client)
{
CheckRequiredEnvironment([
"TESTCREDENTIAL_S3_KEY",
"TESTCREDENTIAL_S3_SECRET",
"TESTCREDENTIAL_S3_BUCKETNAME",
"TESTCREDENTIAL_S3_REGION"
]);
Console.WriteLine( $"s3://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_BUCKETNAME")}/?s3-location-constraint={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_REGION")}&s3-storage-class=&s3-client={client}&auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_KEY")}&auth-password={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_SECRET")}");
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"s3://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_BUCKETNAME")}/?s3-location-constraint={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_REGION")}&s3-storage-class=&s3-client={client}&auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_KEY")}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_SECRET")!)}"
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
return Task.CompletedTask;
}
}
@@ -0,0 +1,86 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.SSH;
/// <summary>
/// SSH Tests
/// </summary>
[TestClass]
public sealed class SshTests : BaseSftpgoTest
{
/// <summary>
/// Tests connecting to SSH using password authentication
/// </summary>
[TestMethod]
public async Task TestSSHWithPassword()
{
var outputConsumer = new OutputConsumer();
var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute |
UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute |
UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute;
var temporaryKeysDir = CreateHttpsCertificates();
CreateUsersFile(temporaryKeysDir);
var container = new ContainerBuilder()
.WithImage("drakkan/sftpgo")
.WithEnvironment("SFTPGO_LOG_LEVEL", "debug")
.WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json")
.WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0")
.WithEnvironment("SFTPGO_SFTPD__BINDINGS__0__PORT", "3333")
.WithPortBinding(3000, 3333)
.WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions)
.WithOutputConsumer(outputConsumer)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(3333))
.Build();
Console.WriteLine("Starting container");
await container.StartAsync();
// Once started we will already cleanup temporary directory
temporaryKeysDir.Delete(true);
Console.WriteLine("Waiting X seconds");
await Task.Delay(TimeSpan.FromSeconds(1));
// Console.WriteLine($"TCP Connect testing, @::1 is open = { await IsPortOpenAsync("::1",3000) }");
// Console.WriteLine($"TCP Connect testing, @localhost is open = { await IsPortOpenAsync("localhost",3000) }");
var exitCode = CommandLine.BackendTester.Program.Main(
new[]
{
$"ssh://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@127.0.0.1:3000",
"--ssh-accept-any-fingerprints"
}.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0)
{
Console.WriteLine(await outputConsumer.GetStreamsOutput());
Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
}
await container.StopAsync();
}
}
@@ -0,0 +1,60 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Utility;
public class OutputConsumer : IOutputConsumer
{
private readonly MemoryStream _stdout = new();
private readonly MemoryStream _stderr = new();
public bool Enabled => true;
public Stream Stdout => _stdout;
public Stream Stderr => _stderr;
public void Dispose()
{
_stdout.Dispose();
_stderr.Dispose();
}
public async Task<string> GetStreamsOutput()
{
var output = new StringBuilder();
_stdout.Position = 0;
using (var reader = new StreamReader(_stdout, leaveOpen: true))
{
output.AppendLine("STDOUT:");
output.AppendLine(await reader.ReadToEndAsync());
}
_stderr.Position = 0;
using (var reader = new StreamReader(_stderr, leaveOpen: true))
{
output.AppendLine("STDERR:");
output.AppendLine(await reader.ReadToEndAsync());
}
return output.ToString();
}
}
@@ -0,0 +1,141 @@
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Backend.Tests.Webdav;
[TestClass]
public sealed class WebDavTests : BaseSftpgoTest
{
[TestMethod]
public async Task TestWebdavHttps()
{
var outputConsumer = new OutputConsumer();
var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute |
UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute |
UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute;
var temporaryKeysDir = CreateHttpsCertificates();
CreateUsersFile(temporaryKeysDir);
var container = new ContainerBuilder()
.WithImage("drakkan/sftpgo")
.WithEnvironment("SFTPGO_LOG_LEVEL", "debug")
.WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__ENABLE_HTTPS", "1")
.WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__PORT", "8090")
.WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__CERTIFICATE_FILE", "fullchain.pem")
.WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__CERTIFICATE_KEY_FILE", "privkey.pem")
.WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json")
.WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0")
.WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions)
.WithPortBinding(8090, 8090)
.WithOutputConsumer(outputConsumer)
.Build();
Console.WriteLine("Starting container");
await container.StartAsync();
// Once started we will already cleanup temporary directory
temporaryKeysDir.Delete(true);
Console.WriteLine("Waiting X seconds");
await Task.Delay(TimeSpan.FromSeconds(5));
// Print logs
Console.WriteLine("Fetching logs...");
var containerStartupLogs = await outputConsumer.GetStreamsOutput();
Console.WriteLine(containerStartupLogs);
// Running this ignoring the certificate
var exitCode = CommandLine.BackendTester.Program.Main(
new[] { $"webdavs://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:8090/", "--accept-any-ssl-certificate=true" }.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode != 0)
{
Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
}
// Running not ignoring the certificate, should return non-zero exit code
exitCode = CommandLine.BackendTester.Program.Main(
new[] { $"webdavs://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:8090/", "--accept-any-ssl-certificate=false" }.Concat(Parameters.GlobalTestParameters).ToArray());
if (exitCode == 0)
{
Assert.Fail("BackendTester is returning zero when it should return non-zero exit code, check logs for details");
}
Console.WriteLine("Stopping container");
await container.StopAsync();
}
[TestMethod]
public async Task TestWebdavHttp()
{
var outputConsumer = new OutputConsumer();
var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute |
UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute |
UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute;
var temporaryKeysDir = CreateHttpsCertificates();
CreateUsersFile(temporaryKeysDir);
var container = new ContainerBuilder()
.WithImage("drakkan/sftpgo")
.WithEnvironment("SFTPGO_LOG_LEVEL", "debug")
.WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__PORT", "8090")
.WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json")
.WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0")
.WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions)
.WithPortBinding(8091, 8090)
.WithOutputConsumer(outputConsumer)
.Build();
Console.WriteLine("Starting container");
await container.StartAsync();
// Once started we will already cleanup temporary directory
temporaryKeysDir.Delete(true);
Console.WriteLine("Waiting X seconds");
await Task.Delay(TimeSpan.FromSeconds(5));
// Print logs
Console.WriteLine("Fetching logs...");
var containerStartupLogs = await outputConsumer.GetStreamsOutput();
Console.WriteLine(containerStartupLogs);
// Running this ignoring the certificate
var exitCode = CommandLine.BackendTester.Program.Main(
["webdav://testuser:testpassword@localhost:8091/"]);
if (exitCode != 0)
{
Assert.Fail("BackendTester is returning non-zero exit code, check logs for details");
}
Console.WriteLine("Stopping container");
await container.StopAsync();
}
}