1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[SM-943] [BEEEP] Swap to SQLite in-memory for integration tests (#3292)

* Swap to sqlite in-memory for integration tests

* Fix integration tests

* Remove EF Core in-memory dependency
This commit is contained in:
Thomas Avery 2023-10-27 11:13:52 -05:00 committed by GitHub
parent ad230fb6a5
commit 1053f49fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 88 additions and 71 deletions

View File

@ -17,7 +17,6 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
public GroupsControllerTests(ScimApplicationFactory factory)
{
_factory = factory;
_factory.DatabaseName = "test_database_groups";
}
public Task InitializeAsync()

View File

@ -17,7 +17,6 @@ public class UsersControllerTests : IClassFixture<ScimApplicationFactory>, IAsyn
public UsersControllerTests(ScimApplicationFactory factory)
{
_factory = factory;
_factory.DatabaseName = "test_database_users";
}
public Task InitializeAsync()

View File

@ -655,14 +655,6 @@
"resolved": "7.0.5",
"contentHash": "yMLM/aK1MikVqpjxd7PJ1Pjgztd3VAd26ZHxyjxG3RPeM9cHjvS5tCg9kAAayR6eHmBg0ffZsHdT28WfA5tTlA=="
},
"Microsoft.EntityFrameworkCore.InMemory": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "7.0.5"
}
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "Transitive",
"resolved": "7.0.5",
@ -3072,7 +3064,6 @@
"Common": "[2023.9.0, )",
"Identity": "[2023.9.0, )",
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )",
"Microsoft.EntityFrameworkCore.InMemory": "[7.0.5, )",
"Microsoft.Extensions.Configuration": "[6.0.1, )"
}
},

View File

@ -2,17 +2,22 @@
using Bit.IntegrationTestCommon.Factories;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Data.Sqlite;
namespace Bit.Api.IntegrationTest.Factories;
public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
{
private readonly IdentityApplicationFactory _identityApplicationFactory;
private const string _connectionString = "DataSource=:memory:";
public ApiApplicationFactory()
{
SqliteConnection = new SqliteConnection(_connectionString);
SqliteConnection.Open();
_identityApplicationFactory = new IdentityApplicationFactory();
_identityApplicationFactory.DatabaseName = DatabaseName;
_identityApplicationFactory.SqliteConnection = SqliteConnection;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
@ -53,4 +58,10 @@ public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
{
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
SqliteConnection.Dispose();
}
}

View File

@ -5,6 +5,7 @@ using Bit.Api.IntegrationTest.SecretsManager.Enums;
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -661,16 +662,15 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = Guid.NewGuid(),
OrganizationId = anotherOrg.Id,
Name = _mockEncryptedString,
});
var request =
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, org.Id, orgUser.Id,
serviceAccount.Id);
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
@ -692,8 +692,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Name = _mockEncryptedString,
});
var request =
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, org.Id, orgUser.Id,
serviceAccount.Id);
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
@ -1086,9 +1085,15 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateProjectAndServiceAccountAsync(Guid organizationId,
bool misMatchOrganization = false)
{
var newOrg = new Organization();
if (misMatchOrganization)
{
newOrg = await _organizationHelper.CreateSmOrganizationAsync();
}
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = misMatchOrganization ? Guid.NewGuid() : organizationId,
OrganizationId = misMatchOrganization ? newOrg.Id : organizationId,
Name = _mockEncryptedString,
});
@ -1127,7 +1132,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
private async Task<AccessPoliciesCreateRequest> SetupUserServiceAccountAccessPolicyRequestAsync(
PermissionType permissionType, Guid organizationId, Guid userId, Guid serviceAccountId)
PermissionType permissionType, Guid userId, Guid serviceAccountId)
{
if (permissionType == PermissionType.RunAsUserWithPermission)
{

View File

@ -189,15 +189,17 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
var project =
await _projectRepository.CreateAsync(new Project { Name = "123", OrganizationId = anotherOrg.Id });
var request = new SecretCreateRequestModel
{
ProjectIds = new Guid[] { project.Id },
ProjectIds = new[] { project.Id },
Key = _mockEncryptedString,
Value = _mockEncryptedString,
Note = _mockEncryptedString,
Note = _mockEncryptedString
};
var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/secrets", request);
@ -594,8 +596,9 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
var project = await _projectRepository.CreateAsync(new Project { Name = "123", OrganizationId = anotherOrg.Id });
var secret = await _secretRepository.CreateAsync(new Secret
{
@ -698,7 +701,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (project, secretIds) = await CreateSecretsAsync(org.Id, 3);
var (project, secretIds) = await CreateSecretsAsync(org.Id);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
@ -709,24 +712,22 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
{
new UserProjectAccessPolicy
{
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true
}
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var response = await _client.PostAsJsonAsync($"/secrets/delete", secretIds);
var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds);
response.EnsureSuccessStatusCode();
var results = await response.Content.ReadFromJsonAsync<ListResponseModel<BulkDeleteResponseModel>>();
Assert.NotNull(results);
var index = 0;
Assert.NotNull(results?.Data);
Assert.Equal(secretIds.Count, results!.Data.Count());
foreach (var result in results!.Data)
{
Assert.Equal(secretIds[index], result.Id);
Assert.Contains(result.Id, secretIds);
Assert.Null(result.Error);
index++;
}
var secrets = await _secretRepository.GetManyByIds(secretIds);

View File

@ -704,14 +704,14 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
Name = _mockEncryptedString
});
var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey
{
ServiceAccountId = org.Id,
ServiceAccountId = serviceAccount.Id,
Name = _mockEncryptedString,
ExpireAt = DateTime.UtcNow.AddDays(30),
ExpireAt = DateTime.UtcNow.AddDays(30)
});
var request = new RevokeAccessTokensRequest
@ -753,9 +753,9 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey
{
ServiceAccountId = org.Id,
ServiceAccountId = serviceAccount.Id,
Name = _mockEncryptedString,
ExpireAt = DateTime.UtcNow.AddDays(30),
ExpireAt = DateTime.UtcNow.AddDays(30)
});
var request = new RevokeAccessTokensRequest

View File

@ -53,6 +53,15 @@ public class SecretsManagerOrganizationHelper
return (_organization, _owner);
}
public async Task<Organization> CreateSmOrganizationAsync()
{
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
await _factory.LoginWithNewAccount(email);
var (organization, owner) =
await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: email, billingEmail: email);
return organization;
}
public async Task<(string email, OrganizationUser orgUser)> CreateNewUser(OrganizationUserType userType, bool accessSecrets)
{
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";

View File

@ -747,14 +747,6 @@
"resolved": "7.0.5",
"contentHash": "yMLM/aK1MikVqpjxd7PJ1Pjgztd3VAd26ZHxyjxG3RPeM9cHjvS5tCg9kAAayR6eHmBg0ffZsHdT28WfA5tTlA=="
},
"Microsoft.EntityFrameworkCore.InMemory": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "7.0.5"
}
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "Transitive",
"resolved": "7.0.5",
@ -3255,7 +3247,6 @@
"Common": "[2023.9.0, )",
"Identity": "[2023.9.0, )",
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )",
"Microsoft.EntityFrameworkCore.InMemory": "[7.0.5, )",
"Microsoft.Extensions.Configuration": "[6.0.1, )"
}
},

View File

@ -655,14 +655,6 @@
"resolved": "7.0.5",
"contentHash": "yMLM/aK1MikVqpjxd7PJ1Pjgztd3VAd26ZHxyjxG3RPeM9cHjvS5tCg9kAAayR6eHmBg0ffZsHdT28WfA5tTlA=="
},
"Microsoft.EntityFrameworkCore.InMemory": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "7.0.5"
}
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "Transitive",
"resolved": "7.0.5",
@ -3072,7 +3064,6 @@
"Common": "[2023.9.0, )",
"Identity": "[2023.9.0, )",
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )",
"Microsoft.EntityFrameworkCore.InMemory": "[7.0.5, )",
"Microsoft.Extensions.Configuration": "[6.0.1, )"
}
},

View File

@ -7,6 +7,7 @@ using Bit.Infrastructure.EntityFramework.Repositories;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -19,7 +20,6 @@ namespace Bit.IntegrationTestCommon.Factories;
public static class FactoryConstants
{
public const string DefaultDatabaseName = "test_database";
public const string WhitelistedIp = "1.1.1.1";
}
@ -27,14 +27,16 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
where T : class
{
/// <summary>
/// The database name to use for this instance of the factory. By default it will use a shared database name so all instances will connect to the same database during it's lifetime.
/// The database to use for this instance of the factory. By default it will use a shared database so all instances will connect to the same database during it's lifetime.
/// </summary>
/// <remarks>
/// This will need to be set BEFORE using the <c>Server</c> property
/// </remarks>
public string DatabaseName { get; set; } = Guid.NewGuid().ToString();
public SqliteConnection SqliteConnection { get; set; }
private readonly List<Action<IServiceCollection>> _configureTestServices = new();
private bool _handleSqliteDisposal { get; set; }
public void SubstitueService<TService>(Action<TService> mockService)
where TService : class
@ -52,10 +54,17 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
}
/// <summary>
/// Configure the web host to use an EF in memory database
/// Configure the web host to use a SQLite in memory database
/// </summary>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
if (SqliteConnection == null)
{
SqliteConnection = new SqliteConnection("DataSource=:memory:");
SqliteConnection.Open();
_handleSqliteDisposal = true;
}
builder.ConfigureAppConfiguration(c =>
{
c.SetBasePath(AppContext.BaseDirectory)
@ -89,11 +98,13 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
services.AddScoped(services =>
{
return new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase(DatabaseName)
.UseSqlite(SqliteConnection)
.UseApplicationServiceProvider(services)
.Options;
});
MigrateDbContext<DatabaseContext>(services);
// QUESTION: The normal licensing service should run fine on developer machines but not in CI
// should we have a fork here to leave the normal service for developers?
// TODO: Eventually add the license file to CI
@ -182,4 +193,23 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
var scope = Services.CreateScope();
return scope.ServiceProvider.GetRequiredService<TS>();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_handleSqliteDisposal)
{
SqliteConnection.Dispose();
}
}
private static void MigrateDbContext<TContext>(IServiceCollection serviceCollection) where TContext : DbContext
{
var serviceProvider = serviceCollection.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var services = scope.ServiceProvider;
var context = services.GetService<TContext>();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
}

View File

@ -6,7 +6,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
</ItemGroup>

View File

@ -13,15 +13,6 @@
"Microsoft.Extensions.Hosting": "6.0.1"
}
},
"Microsoft.EntityFrameworkCore.InMemory": {
"type": "Direct",
"requested": "[7.0.5, )",
"resolved": "7.0.5",
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "7.0.5"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Direct",
"requested": "[6.0.1, )",