mirror of
https://github.com/bitwarden/server.git
synced 2024-11-24 12:35:25 +01:00
[SM-153] Add scaffolded API integration test project (#2209)
This commit is contained in:
parent
194c695cd0
commit
7c4521e0b4
@ -94,6 +94,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scim", "bitwarden_license\s
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegration.Test", "test\Infrastructure.EFIntegration.Test\Infrastructure.EFIntegration.Test.csproj", "{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegration.Test", "test\Infrastructure.EFIntegration.Test\Infrastructure.EFIntegration.Test.csproj", "{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.IntegrationTest", "test\Api.IntegrationTest\Api.IntegrationTest.csproj", "{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -226,6 +228,10 @@ Global
|
|||||||
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -262,6 +268,7 @@ Global
|
|||||||
{0923DE59-5FB1-44F2-9302-A09D2236B470} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
{0923DE59-5FB1-44F2-9302-A09D2236B470} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
{BC3B3F8C-621A-4CB8-9563-6EC0A2C8C747} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
|
{BC3B3F8C-621A-4CB8-9563-6EC0A2C8C747} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
|
||||||
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
{7EFB1124-F40A-40EB-9EDA-94FD540AA8FD} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
|
{CBE96C6D-A4D6-46E1-94C5-42D6CAD8531C} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
||||||
|
@ -40,7 +40,7 @@ namespace Bit.Api.Controllers
|
|||||||
var result = new OrganizationExportResponseModel
|
var result = new OrganizationExportResponseModel
|
||||||
{
|
{
|
||||||
Collections = GetOrganizationCollectionsResponse(orgCollections),
|
Collections = GetOrganizationCollectionsResponse(orgCollections),
|
||||||
Ciphers = await GetOrganizationCiphersResponse(orgCiphers, collectionCiphersGroupDict)
|
Ciphers = GetOrganizationCiphersResponse(orgCiphers, collectionCiphersGroupDict)
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -52,7 +52,7 @@ namespace Bit.Api.Controllers
|
|||||||
return new ListResponseModel<CollectionResponseModel>(collections);
|
return new ListResponseModel<CollectionResponseModel>(collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ListResponseModel<CipherMiniDetailsResponseModel>> GetOrganizationCiphersResponse(IEnumerable<CipherOrganizationDetails> orgCiphers,
|
private ListResponseModel<CipherMiniDetailsResponseModel> GetOrganizationCiphersResponse(IEnumerable<CipherOrganizationDetails> orgCiphers,
|
||||||
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict)
|
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict)
|
||||||
{
|
{
|
||||||
var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings,
|
var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings,
|
||||||
|
@ -40,6 +40,10 @@ namespace Bit.Api.Models.Response
|
|||||||
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
|
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProfileResponseModel() : base("profile")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
29
test/Api.IntegrationTest/Api.IntegrationTest.csproj
Normal file
29
test/Api.IntegrationTest/Api.IntegrationTest.csproj
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||||
|
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Api\Api.csproj" />
|
||||||
|
<ProjectReference Include="..\IntegrationTestCommon\IntegrationTestCommon.csproj" />
|
||||||
|
|
||||||
|
<Content Include="..\..\src\Api\appsettings.*.json">
|
||||||
|
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,40 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Bit.Api.IntegrationTest.Factories;
|
||||||
|
using Bit.Api.Models.Response;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Api.IntegrationTest.Controllers;
|
||||||
|
|
||||||
|
public class AccountsControllerTest : IClassFixture<ApiApplicationFactory>
|
||||||
|
{
|
||||||
|
private readonly ApiApplicationFactory _factory;
|
||||||
|
|
||||||
|
public AccountsControllerTest(ApiApplicationFactory factory) => _factory = factory;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPublicKey()
|
||||||
|
{
|
||||||
|
var tokens = await _factory.LoginWithNewAccount();
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
|
||||||
|
using var message = new HttpRequestMessage(HttpMethod.Get, "/accounts/profile");
|
||||||
|
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
|
||||||
|
var response = await client.SendAsync(message);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var content = await response.Content.ReadFromJsonAsync<ProfileResponseModel>();
|
||||||
|
|
||||||
|
Assert.NotEmpty(content.Id);
|
||||||
|
Assert.Equal("integration-test@bitwarden.com", content.Email);
|
||||||
|
Assert.Null(content.Name);
|
||||||
|
Assert.False(content.EmailVerified);
|
||||||
|
Assert.False(content.Premium);
|
||||||
|
Assert.False(content.PremiumFromOrganization);
|
||||||
|
Assert.Null(content.MasterPasswordHint);
|
||||||
|
Assert.Equal("en-US", content.Culture);
|
||||||
|
Assert.Null(content.Key);
|
||||||
|
Assert.Null(content.PrivateKey);
|
||||||
|
Assert.NotNull(content.SecurityStamp);
|
||||||
|
}
|
||||||
|
}
|
44
test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs
Normal file
44
test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using Bit.Core.Models.Api.Request.Accounts;
|
||||||
|
using Bit.IntegrationTestCommon.Factories;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
|
||||||
|
namespace Bit.Api.IntegrationTest.Factories
|
||||||
|
{
|
||||||
|
public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||||
|
{
|
||||||
|
private readonly IdentityApplicationFactory _identityApplicationFactory;
|
||||||
|
|
||||||
|
public ApiApplicationFactory()
|
||||||
|
{
|
||||||
|
_identityApplicationFactory = new IdentityApplicationFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
base.ConfigureWebHost(builder);
|
||||||
|
|
||||||
|
builder.ConfigureTestServices(services =>
|
||||||
|
{
|
||||||
|
services.PostConfigure<IdentityServerAuthenticationOptions>(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
options.JwtBackChannelHandler = _identityApplicationFactory.Server.CreateHandler();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper for registering and logging in to a new account
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
|
||||||
|
{
|
||||||
|
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3196
test/Api.IntegrationTest/packages.lock.json
Normal file
3196
test/Api.IntegrationTest/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -35,13 +35,18 @@ namespace Bit.IntegrationTestCommon.Factories
|
|||||||
{
|
{
|
||||||
builder.ConfigureAppConfiguration(c =>
|
builder.ConfigureAppConfiguration(c =>
|
||||||
{
|
{
|
||||||
|
c.SetBasePath(AppContext.BaseDirectory)
|
||||||
|
.AddJsonFile("appsettings.json")
|
||||||
|
.AddJsonFile("appsettings.Development.json");
|
||||||
|
|
||||||
|
c.AddUserSecrets(typeof(Identity.Startup).Assembly, optional: true);
|
||||||
c.AddInMemoryCollection(new Dictionary<string, string>
|
c.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
// Manually insert a EF provider so that ConfigureServices will add EF repositories but we will override
|
// Manually insert a EF provider so that ConfigureServices will add EF repositories but we will override
|
||||||
// DbContextOptions to use an in memory database
|
// DbContextOptions to use an in memory database
|
||||||
{ "globalSettings:databaseProvider", "postgres" },
|
{ "globalSettings:databaseProvider", "postgres" },
|
||||||
{ "globalSettings:postgreSql:connectionString", "Host=localhost;Username=test;Password=test;Database=test" },
|
{ "globalSettings:postgreSql:connectionString", "Host=localhost;Username=test;Password=test;Database=test" },
|
||||||
|
|
||||||
// Clear the redis connection string for distributed caching, forcing an in-memory implementation
|
// Clear the redis connection string for distributed caching, forcing an in-memory implementation
|
||||||
{ "globalSettings:redis:connectionString", ""}
|
{ "globalSettings:redis:connectionString", ""}
|
||||||
});
|
});
|
||||||
@ -82,7 +87,7 @@ namespace Bit.IntegrationTestCommon.Factories
|
|||||||
services.AddSingleton<IEventRepository, EventRepository>();
|
services.AddSingleton<IEventRepository, EventRepository>();
|
||||||
|
|
||||||
// Our Rate limiter works so well that it begins to fail tests unless we carve out
|
// Our Rate limiter works so well that it begins to fail tests unless we carve out
|
||||||
// one whitelisted ip. We should still test the rate limiter though and they should change the Ip
|
// one whitelisted ip. We should still test the rate limiter though and they should change the Ip
|
||||||
// to something that is NOT whitelisted
|
// to something that is NOT whitelisted
|
||||||
services.Configure<IpRateLimitOptions>(options =>
|
services.Configure<IpRateLimitOptions>(options =>
|
||||||
{
|
{
|
||||||
@ -91,6 +96,9 @@ namespace Bit.IntegrationTestCommon.Factories
|
|||||||
FactoryConstants.WhitelistedIp,
|
FactoryConstants.WhitelistedIp,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix IP Rate Limiting
|
||||||
|
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
test/IntegrationTestCommon/FakeRemoteIpAddressMiddleware.cs
Normal file
36
test/IntegrationTestCommon/FakeRemoteIpAddressMiddleware.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Bit.IntegrationTestCommon;
|
||||||
|
|
||||||
|
public class FakeRemoteIpAddressMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IPAddress _fakeIpAddress;
|
||||||
|
|
||||||
|
public FakeRemoteIpAddressMiddleware(RequestDelegate next, IPAddress fakeIpAddress = null)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_fakeIpAddress = fakeIpAddress ?? IPAddress.Parse("127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
httpContext.Connection.RemoteIpAddress ??= _fakeIpAddress;
|
||||||
|
await _next(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomStartupFilter : IStartupFilter
|
||||||
|
{
|
||||||
|
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
||||||
|
{
|
||||||
|
return app =>
|
||||||
|
{
|
||||||
|
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
|
||||||
|
next(app);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTestCommon", "In
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegration.Test", "Infrastructure.EFIntegration.Test\Infrastructure.EFIntegration.Test.csproj", "{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegration.Test", "Infrastructure.EFIntegration.Test\Infrastructure.EFIntegration.Test.csproj", "{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.IntegrationTest", "Api.IntegrationTest\Api.IntegrationTest.csproj", "{6ED94433-3423-498C-96C9-F24756357D95}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -128,5 +130,17 @@ Global
|
|||||||
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x64.Build.0 = Release|Any CPU
|
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.ActiveCfg = Release|Any CPU
|
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.Build.0 = Release|Any CPU
|
{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6ED94433-3423-498C-96C9-F24756357D95}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Loading…
Reference in New Issue
Block a user