mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-2444] Add Pipeline for Testing All Database Variants in CI (#2471)
* Add Pipeline * Fix Lint * Added a Change * Update Pipeline * Add Multi-Version Support * Use Profile Switch for each profile * Fix MySql * Debug MySql * Use Proper Seperator * Add Allow User Variables=true * Pipeline Work * Expand Config for Postgres * Change Config Key * Add Debug Step * Fix Debug Step * Fix Tests * Add Sleep * Fix Tests * Fix SQL Server Tests * Add Sqlite * Use Context Property * Fix Tests * Fix Test Logger * Update AccountRevisionDate Check * Fix Postgres Time Issues * Formatting and Pipeline Update * Remove Unneeded SqlServer Setting * Update .github/workflows/infrastructure-tests.yml Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com> --------- Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>
This commit is contained in:
@ -16,7 +16,7 @@ public class EmergencyAccessRepositoriesTests
|
||||
var grantorUser = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantor User",
|
||||
Email = "test+grantor@email.com",
|
||||
Email = $"test+grantor{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
@ -24,7 +24,7 @@ public class EmergencyAccessRepositoriesTests
|
||||
var granteeUser = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test Grantee User",
|
||||
Email = "test+grantee@email.com",
|
||||
Email = $"test+grantee{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
@ -1,17 +1,31 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Bit.Core.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest;
|
||||
|
||||
public class Database
|
||||
{
|
||||
public SupportedDatabaseProviders Type { get; set; }
|
||||
public string ConnectionString { get; set; } = default!;
|
||||
public bool UseEf { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
internal class TypedConfig
|
||||
{
|
||||
public Database[] Databases { get; set; } = default!;
|
||||
}
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static bool TryGetConnectionString(this IConfiguration config, string key, out string connectionString)
|
||||
public static Database[] GetDatabases(this IConfiguration config)
|
||||
{
|
||||
connectionString = config[key];
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
var typedConfig = config.Get<TypedConfig>();
|
||||
if (typedConfig.Databases == null)
|
||||
{
|
||||
return false;
|
||||
return Array.Empty<Database>();
|
||||
}
|
||||
|
||||
return true;
|
||||
return typedConfig.Databases.Where(d => d.Enabled).ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Infrastructure.Dapper;
|
||||
using Bit.Infrastructure.EntityFramework;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -45,42 +46,39 @@ public class DatabaseDataAttribute : DataAttribute
|
||||
}
|
||||
};
|
||||
|
||||
if (config.TryGetConnectionString(DatabaseTheoryAttribute.DapperSqlServerKey, out var dapperSqlServerConnectionString))
|
||||
var databases = config.GetDatabases();
|
||||
|
||||
foreach (var database in databases)
|
||||
{
|
||||
var dapperSqlServerCollection = new ServiceCollection();
|
||||
dapperSqlServerCollection.AddLogging(configureLogging);
|
||||
dapperSqlServerCollection.AddDapperRepositories(SelfHosted);
|
||||
var globalSettings = new GlobalSettings
|
||||
if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf)
|
||||
{
|
||||
DatabaseProvider = "sqlServer",
|
||||
SqlServer = new GlobalSettings.SqlSettings
|
||||
var dapperSqlServerCollection = new ServiceCollection();
|
||||
dapperSqlServerCollection.AddLogging(configureLogging);
|
||||
dapperSqlServerCollection.AddDapperRepositories(SelfHosted);
|
||||
var globalSettings = new GlobalSettings
|
||||
{
|
||||
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.SetupEntityFramework(efPostgresConnectionString, SupportedDatabaseProviders.Postgres);
|
||||
efPostgresCollection.AddPasswordManagerEFRepositories(SelfHosted);
|
||||
efPostgresCollection.AddTransient<ITestDatabaseHelper, EfTestDatabaseHelper>();
|
||||
yield return efPostgresCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
if (config.TryGetConnectionString(DatabaseTheoryAttribute.EfMySqlKey, out var efMySqlConnectionString))
|
||||
{
|
||||
var efMySqlCollection = new ServiceCollection();
|
||||
efMySqlCollection.AddLogging(configureLogging);
|
||||
efMySqlCollection.SetupEntityFramework(efMySqlConnectionString, SupportedDatabaseProviders.MySql);
|
||||
efMySqlCollection.AddPasswordManagerEFRepositories(SelfHosted);
|
||||
efMySqlCollection.AddTransient<ITestDatabaseHelper, EfTestDatabaseHelper>();
|
||||
yield return efMySqlCollection.BuildServiceProvider();
|
||||
DatabaseProvider = "sqlServer",
|
||||
SqlServer = new GlobalSettings.SqlSettings
|
||||
{
|
||||
ConnectionString = database.ConnectionString,
|
||||
},
|
||||
};
|
||||
dapperSqlServerCollection.AddSingleton(globalSettings);
|
||||
dapperSqlServerCollection.AddSingleton<IGlobalSettings>(globalSettings);
|
||||
dapperSqlServerCollection.AddSingleton<ITestDatabaseHelper>(_ => new DapperSqlServerTestDatabaseHelper(database));
|
||||
dapperSqlServerCollection.AddDataProtection();
|
||||
yield return dapperSqlServerCollection.BuildServiceProvider();
|
||||
}
|
||||
else
|
||||
{
|
||||
var efCollection = new ServiceCollection();
|
||||
efCollection.AddLogging(configureLogging);
|
||||
efCollection.SetupEntityFramework(database.ConnectionString, database.Type);
|
||||
efCollection.AddPasswordManagerEFRepositories(SelfHosted);
|
||||
efCollection.AddTransient<ITestDatabaseHelper>(sp => new EfTestDatabaseHelper(sp.GetRequiredService<DatabaseContext>(), database));
|
||||
efCollection.AddDataProtection();
|
||||
yield return efCollection.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,24 +7,18 @@ 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.";
|
||||
Skip = "No databases setup.";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasAnyDatabaseSetup()
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
return config.TryGetConnectionString(DapperSqlServerKey, out _) ||
|
||||
config.TryGetConnectionString(EfPostgresKey, out _) ||
|
||||
config.TryGetConnectionString(EfMySqlKey, out _);
|
||||
return config.GetDatabases().Length > 0;
|
||||
}
|
||||
|
||||
public static IConfiguration GetConfiguration()
|
||||
|
@ -16,7 +16,7 @@ public class OrganizationUserRepositoryTests
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = "test@email.com",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
@ -24,6 +24,8 @@ public class OrganizationUserRepositoryTests
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
BillingEmail = user.Email, // TODO: EF does not enfore this being NOT NULL
|
||||
Plan = "Test", // TODO: EF does not enforce this being NOT NULl
|
||||
});
|
||||
|
||||
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
@ -50,7 +52,7 @@ public class OrganizationUserRepositoryTests
|
||||
var user1 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User 1",
|
||||
Email = "test1@email.com",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
@ -58,7 +60,7 @@ public class OrganizationUserRepositoryTests
|
||||
var user2 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User 2",
|
||||
Email = "test1@email.com",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
@ -66,18 +68,22 @@ public class OrganizationUserRepositoryTests
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
BillingEmail = user1.Email, // TODO: EF does not enforce this being NOT NULl
|
||||
Plan = "Test", // TODO: EF does not enforce this being NOT NULl
|
||||
});
|
||||
|
||||
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user1.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
});
|
||||
|
||||
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user2.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
});
|
||||
|
||||
helper.ClearTracker();
|
||||
|
@ -4,6 +4,7 @@ namespace Bit.Infrastructure.IntegrationTest;
|
||||
|
||||
public interface ITestDatabaseHelper
|
||||
{
|
||||
Database Info { get; }
|
||||
void ClearTracker();
|
||||
}
|
||||
|
||||
@ -11,11 +12,14 @@ public class EfTestDatabaseHelper : ITestDatabaseHelper
|
||||
{
|
||||
private readonly DatabaseContext _databaseContext;
|
||||
|
||||
public EfTestDatabaseHelper(DatabaseContext databaseContext)
|
||||
public EfTestDatabaseHelper(DatabaseContext databaseContext, Database database)
|
||||
{
|
||||
_databaseContext = databaseContext;
|
||||
Info = database;
|
||||
}
|
||||
|
||||
public Database Info { get; }
|
||||
|
||||
public void ClearTracker()
|
||||
{
|
||||
_databaseContext.ChangeTracker.Clear();
|
||||
@ -24,11 +28,13 @@ public class EfTestDatabaseHelper : ITestDatabaseHelper
|
||||
|
||||
public class DapperSqlServerTestDatabaseHelper : ITestDatabaseHelper
|
||||
{
|
||||
public DapperSqlServerTestDatabaseHelper()
|
||||
public DapperSqlServerTestDatabaseHelper(Database database)
|
||||
{
|
||||
|
||||
Info = database;
|
||||
}
|
||||
|
||||
public Database Info { get; }
|
||||
|
||||
public void ClearTracker()
|
||||
{
|
||||
// There are no tracked entities in Dapper SQL Server
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
@ -20,7 +21,7 @@ public class CipherRepositoryTests
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = "test@email.com",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
@ -29,6 +30,7 @@ public class CipherRepositoryTests
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
UserId = user.Id,
|
||||
Data = "", // TODO: EF does not enforce this as NOT NULL
|
||||
});
|
||||
|
||||
helper.ClearTracker();
|
||||
@ -55,21 +57,27 @@ public class CipherRepositoryTests
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = "test@email.com",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
helper.ClearTracker();
|
||||
|
||||
user = await userRepository.GetByIdAsync(user.Id);
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Organization",
|
||||
BillingEmail = user.Email,
|
||||
Plan = "Test" // TODO: EF does not enforce this as NOT NULL
|
||||
});
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
UserId = user.Id,
|
||||
OrganizationId = organization.Id,
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Owner,
|
||||
});
|
||||
|
||||
@ -79,12 +87,27 @@ public class CipherRepositoryTests
|
||||
OrganizationId = organization.Id
|
||||
});
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await collectionRepository.UpdateUsersAsync(collection.Id, new[]
|
||||
{
|
||||
new CollectionAccessSelection
|
||||
{
|
||||
Id = orgUser.Id,
|
||||
HidePasswords = true,
|
||||
ReadOnly = true,
|
||||
},
|
||||
});
|
||||
|
||||
helper.ClearTracker();
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await cipherRepository.CreateAsync(new CipherDetails
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = "", // TODO: EF does not enforce this as NOT NULL
|
||||
}, new List<Guid>
|
||||
{
|
||||
collection.Id,
|
||||
@ -92,7 +115,8 @@ public class CipherRepositoryTests
|
||||
|
||||
var updatedUser = await userRepository.GetByIdAsync(user.Id);
|
||||
|
||||
Assert.NotEqual(updatedUser.AccountRevisionDate, user.AccountRevisionDate);
|
||||
Assert.True(updatedUser.AccountRevisionDate - user.AccountRevisionDate > TimeSpan.Zero,
|
||||
"The AccountRevisionDate is expected to be changed");
|
||||
|
||||
var collectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
|
||||
Assert.NotEmpty(collectionCiphers);
|
||||
|
Reference in New Issue
Block a user