1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

[PS-1928] Add BumpAccountRevisionDate methods (#2458)

* Move RevisionDate Bumps to Extension Class

* Add Tests against live databases

* Run Formatting

* Fix Typo

* Fix Test Solution Typo

* Await ReplaceAsync
This commit is contained in:
Justin Baur
2022-12-02 14:24:30 -05:00
committed by GitHub
parent 41db511872
commit efe91fd0d8
25 changed files with 3788 additions and 309 deletions

View File

@ -0,0 +1,17 @@
using Microsoft.Extensions.Configuration;
namespace Bit.Infrastructure.IntegrationTest;
public static class ConfigurationExtensions
{
public static bool TryGetConnectionString(this IConfiguration config, string key, out string connectionString)
{
connectionString = config[key];
if (string.IsNullOrEmpty(connectionString))
{
return false;
}
return true;
}
}

View File

@ -0,0 +1,84 @@
using System.Reflection;
using Bit.Core.Enums;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper;
using Bit.Infrastructure.EntityFramework;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit.Sdk;
namespace Bit.Infrastructure.IntegrationTest;
public class DatabaseDataAttribute : DataAttribute
{
public bool SelfHosted { get; set; }
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
var parameters = testMethod.GetParameters();
var config = DatabaseTheoryAttribute.GetConfiguration();
var serviceProviders = GetDatabaseProviders(config);
foreach (var provider in serviceProviders)
{
var objects = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
objects[i] = provider.GetRequiredService(parameters[i].ParameterType);
}
yield return objects;
}
}
private IEnumerable<IServiceProvider> GetDatabaseProviders(IConfiguration config)
{
var configureLogging = (ILoggingBuilder builder) =>
{
if (!config.GetValue<bool>("Quiet"))
{
builder.AddConfiguration(config);
builder.AddConsole();
builder.AddDebug();
}
};
if (config.TryGetConnectionString(DatabaseTheoryAttribute.DapperSqlServerKey, out var dapperSqlServerConnectionString))
{
var dapperSqlServerCollection = new ServiceCollection();
dapperSqlServerCollection.AddLogging(configureLogging);
dapperSqlServerCollection.AddDapperRepositories(SelfHosted);
var globalSettings = new GlobalSettings
{
DatabaseProvider = "sqlServer",
SqlServer = new GlobalSettings.SqlSettings
{
ConnectionString = dapperSqlServerConnectionString,
},
};
dapperSqlServerCollection.AddSingleton(globalSettings);
dapperSqlServerCollection.AddSingleton<IGlobalSettings>(globalSettings);
yield return dapperSqlServerCollection.BuildServiceProvider();
}
if (config.TryGetConnectionString(DatabaseTheoryAttribute.EfPostgresKey, out var efPostgresConnectionString))
{
var efPostgresCollection = new ServiceCollection();
efPostgresCollection.AddLogging(configureLogging);
efPostgresCollection.AddEFRepositories(SelfHosted, efPostgresConnectionString, SupportedDatabaseProviders.Postgres);
efPostgresCollection.AddTransient<ITestDatabaseHelper, EfTestDatabaseHelper>();
yield return efPostgresCollection.BuildServiceProvider();
}
if (config.TryGetConnectionString(DatabaseTheoryAttribute.EfMySqlKey, out var efMySqlConnectionString))
{
var efMySqlCollection = new ServiceCollection();
efMySqlCollection.AddLogging(configureLogging);
efMySqlCollection.AddEFRepositories(SelfHosted, efMySqlConnectionString, SupportedDatabaseProviders.MySql);
efMySqlCollection.AddTransient<ITestDatabaseHelper, EfTestDatabaseHelper>();
yield return efMySqlCollection.BuildServiceProvider();
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.Extensions.Configuration;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest;
public class DatabaseTheoryAttribute : TheoryAttribute
{
private static IConfiguration? _cachedConfiguration;
public const string DapperSqlServerKey = "Dapper:SqlServer";
public const string EfPostgresKey = "Ef:Postgres";
public const string EfMySqlKey = "Ef:MySql";
public DatabaseTheoryAttribute()
{
if (!HasAnyDatabaseSetup())
{
Skip = "No database connections strings setup.";
}
}
private static bool HasAnyDatabaseSetup()
{
var config = GetConfiguration();
return config.TryGetConnectionString(DapperSqlServerKey, out _) ||
config.TryGetConnectionString(EfPostgresKey, out _) ||
config.TryGetConnectionString(EfMySqlKey, out _);
}
public static IConfiguration GetConfiguration()
{
return _cachedConfiguration ??= new ConfigurationBuilder()
.AddUserSecrets<DatabaseDataAttribute>(optional: true, reloadOnChange: false)
.AddEnvironmentVariables("BW_TEST_")
.AddCommandLine(Environment.GetCommandLineArgs())
.Build();
}
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<UserSecretsId>6570f288-5c2c-47ad-8978-f3da255079c2</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Infrastructure.Dapper\Infrastructure.Dapper.csproj" />
<ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,97 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Core.Models.Data;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Repositories;
public class CipherRepositoryTests
{
[DatabaseTheory, DatabaseData]
public async Task DeleteAsync_UpdatesUserRevisionDate(
IUserRepository userRepository,
ICipherRepository cipherRepository,
ITestDatabaseHelper helper)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = "test@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
UserId = user.Id,
});
helper.ClearTracker();
await cipherRepository.DeleteAsync(cipher);
var deletedCipher = await cipherRepository.GetByIdAsync(cipher.Id);
Assert.Null(deletedCipher);
var updatedUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotEqual(updatedUser.AccountRevisionDate, user.AccountRevisionDate);
}
[DatabaseTheory, DatabaseData]
public async Task CreateAsync_UpdateWithCollecitons_Works(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICipherRepository cipherRepository,
ICollectionCipherRepository collectionCipherRepository,
ITestDatabaseHelper helper)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = "test@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
});
await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Accepted,
Type = OrganizationUserType.Owner,
});
var collection = await collectionRepository.CreateAsync(new Collection
{
Name = "Test Collection",
OrganizationId = organization.Id
});
helper.ClearTracker();
await cipherRepository.CreateAsync(new CipherDetails
{
Type = CipherType.Login,
OrganizationId = organization.Id,
}, new List<Guid>
{
collection.Id,
});
var updatedUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotEqual(updatedUser.AccountRevisionDate, user.AccountRevisionDate);
var collectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
Assert.NotEmpty(collectionCiphers);
}
}

View File

@ -0,0 +1,46 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Repositories;
public class EmergencyAccessRepositoriesTests
{
[DatabaseTheory, DatabaseData]
public async Task DeleteAsync_UpdatesRevisionDate(IUserRepository userRepository,
IEmergencyAccessRepository emergencyAccessRepository,
ITestDatabaseHelper helper)
{
var grantorUser = await userRepository.CreateAsync(new User
{
Name = "Test Grantor User",
Email = "test+grantor@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var granteeUser = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User",
Email = "test+grantee@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var emergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
GranteeId = granteeUser.Id,
Status = EmergencyAccessStatusType.Confirmed,
});
helper.ClearTracker();
await emergencyAccessRepository.DeleteAsync(emergencyAccess);
var updatedGrantee = await userRepository.GetByIdAsync(granteeUser.Id);
Assert.NotEqual(updatedGrantee.AccountRevisionDate, granteeUser.AccountRevisionDate);
}
}

View File

@ -0,0 +1,97 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Repositories;
public class OrganizationUserRepositoryTests
{
[DatabaseTheory, DatabaseData]
public async Task DeleteAsync_Works(IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ITestDatabaseHelper helper)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = "test@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
});
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
{
OrganizationId = organization.Id,
UserId = user.Id,
Status = OrganizationUserStatusType.Confirmed,
});
helper.ClearTracker();
await organizationUserRepository.DeleteAsync(orgUser);
var newUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotEqual(newUser.AccountRevisionDate, user.AccountRevisionDate);
}
[DatabaseTheory, DatabaseData]
public async Task DeleteManyAsync_Works(IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ITestDatabaseHelper helper)
{
var user1 = await userRepository.CreateAsync(new User
{
Name = "Test User 1",
Email = "test1@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var user2 = await userRepository.CreateAsync(new User
{
Name = "Test User 2",
Email = "test1@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
});
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
OrganizationId = organization.Id,
UserId = user1.Id,
});
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
OrganizationId = organization.Id,
UserId = user2.Id,
});
helper.ClearTracker();
await organizationUserRepository.DeleteManyAsync(new List<Guid>
{
orgUser1.Id,
orgUser2.Id,
});
var updatedUser1 = await userRepository.GetByIdAsync(user1.Id);
var updatedUser2 = await userRepository.GetByIdAsync(user2.Id);
Assert.NotEqual(updatedUser1.AccountRevisionDate, user1.AccountRevisionDate);
Assert.NotEqual(updatedUser2.AccountRevisionDate, user2.AccountRevisionDate);
}
}

View File

@ -0,0 +1,36 @@
using Bit.Infrastructure.EntityFramework.Repositories;
namespace Bit.Infrastructure.IntegrationTest;
public interface ITestDatabaseHelper
{
void ClearTracker();
}
public class EfTestDatabaseHelper : ITestDatabaseHelper
{
private readonly DatabaseContext _databaseContext;
public EfTestDatabaseHelper(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public void ClearTracker()
{
_databaseContext.ChangeTracker.Clear();
}
}
public class DapperSqlServerTestDatabaseHelper : ITestDatabaseHelper
{
public DapperSqlServerTestDatabaseHelper()
{
}
public void ClearTracker()
{
// There are no tracked entities in Dapper SQL Server
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegratio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.IntegrationTest", "Api.IntegrationTest\Api.IntegrationTest.csproj", "{6ED94433-3423-498C-96C9-F24756357D95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.IntegrationTest", "Infrastructure.IntegrationTest\Infrastructure.IntegrationTest.csproj", "{5827E256-D1C5-4BBE-BB74-ED28A83578FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -142,5 +144,17 @@ Global
{6ED94433-3423-498C-96C9-F24756357D95}.Release|x64.Build.0 = Release|Any CPU
{6ED94433-3423-498C-96C9-F24756357D95}.Release|x86.ActiveCfg = Release|Any CPU
{6ED94433-3423-498C-96C9-F24756357D95}.Release|x86.Build.0 = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x64.ActiveCfg = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x64.Build.0 = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x86.Build.0 = Debug|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|Any CPU.Build.0 = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x64.ActiveCfg = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x64.Build.0 = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.ActiveCfg = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal