Add some integration tests for the Server project (#6839)

* Add some integration tests for the Server project

* Not sure why this project got removed?

* Format

* capture debug output

* Update tests to work with the now legacy WebHostBuilder

- I accidentally had the updated Program locally and that was why tests were working for me locally

* Formatting...again
This commit is contained in:
Justin Baur
2026-01-15 03:52:00 -05:00
committed by GitHub
parent b86a31160a
commit 44249c38e0
6 changed files with 187 additions and 2 deletions

View File

@@ -140,10 +140,13 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi\SeederApi.csproj", "{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi.IntegrationTest", "test\SeederApi.IntegrationTest\SeederApi.IntegrationTest.csproj", "{A2E067EF-609C-4D13-895A-E054C61D48BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO.Test", "bitwarden_license\test\SSO.Test\SSO.Test.csproj", "{7D98784C-C253-43FB-9873-25B65C6250D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitwarden_license\test\Sso.IntegrationTest\Sso.IntegrationTest.csproj", "{FFB09376-595B-6F93-36F0-70CAE90AFECB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.IntegrationTest", "test\Server.IntegrationTest\Server.IntegrationTest.csproj", "{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -372,6 +375,10 @@ Global
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.Build.0 = Release|Any CPU
{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -432,6 +439,7 @@ Global
{A2E067EF-609C-4D13-895A-E054C61D48BB} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{7D98784C-C253-43FB-9873-25B65C6250D6} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
{FFB09376-595B-6F93-36F0-70CAE90AFECB} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

View File

@@ -0,0 +1 @@
[assembly: CaptureTrace]

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="xunit.v3" Version="3.0.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\util\Server\Server.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
namespace Bit.Server.IntegrationTest;
public class Server : WebApplicationFactory<Program>
{
public string? ContentRoot { get; set; }
public string? WebRoot { get; set; }
public bool ServeUnknown { get; set; }
public bool? WebVault { get; set; }
public string? AppIdLocation { get; set; }
protected override IWebHostBuilder? CreateWebHostBuilder()
{
var args = new List<string>
{
"/contentRoot",
ContentRoot ?? "",
"/webRoot",
WebRoot ?? "",
"/serveUnknown",
ServeUnknown.ToString().ToLowerInvariant(),
};
if (WebVault.HasValue)
{
args.Add("/webVault");
args.Add(WebVault.Value.ToString().ToLowerInvariant());
}
if (!string.IsNullOrEmpty(AppIdLocation))
{
args.Add("/appIdLocation");
args.Add(AppIdLocation);
}
var builder = WebHostBuilderFactory.CreateFromTypesAssemblyEntryPoint<Program>([.. args])
?? throw new InvalidProgramException("Could not create builder from assembly.");
builder.UseSetting("TEST_CONTENTROOT_SERVER", ContentRoot);
return builder;
}
}

View File

@@ -0,0 +1,102 @@
using System.Net;
using System.Runtime.CompilerServices;
namespace Bit.Server.IntegrationTest;
public class ServerTests
{
[Fact]
public async Task AttachmentsStyleUse()
{
using var tempDir = new TempDir();
await tempDir.WriteAsync("my-file.txt", "Hello!");
using var server = new Server
{
ContentRoot = tempDir.Info.FullName,
WebRoot = ".",
ServeUnknown = true,
};
var client = server.CreateClient();
var response = await client.GetAsync("/my-file.txt", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello!", await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken));
}
[Fact]
public async Task WebVaultStyleUse()
{
using var tempDir = new TempDir();
await tempDir.WriteAsync("index.html", "<html></html>");
await tempDir.WriteAsync(Path.Join("app", "file.js"), "AppStuff");
await tempDir.WriteAsync(Path.Join("locales", "file.json"), "LocalesStuff");
await tempDir.WriteAsync(Path.Join("fonts", "file.ttf"), "FontsStuff");
await tempDir.WriteAsync(Path.Join("connectors", "file.js"), "ConnectorsStuff");
await tempDir.WriteAsync(Path.Join("scripts", "file.js"), "ScriptsStuff");
await tempDir.WriteAsync(Path.Join("images", "file.avif"), "ImagesStuff");
await tempDir.WriteAsync(Path.Join("test", "file.json"), "{}");
using var server = new Server
{
ContentRoot = tempDir.Info.FullName,
WebRoot = ".",
ServeUnknown = false,
WebVault = true,
AppIdLocation = Path.Join(tempDir.Info.FullName, "test", "file.json"),
};
var client = server.CreateClient();
// Going to root should return the default file
var response = await client.GetAsync("", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("<html></html>", await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken));
// No caching on the default document
Assert.Null(response.Headers.CacheControl?.MaxAge);
await ExpectMaxAgeAsync("app/file.js", TimeSpan.FromDays(14));
await ExpectMaxAgeAsync("locales/file.json", TimeSpan.FromDays(14));
await ExpectMaxAgeAsync("fonts/file.ttf", TimeSpan.FromDays(14));
await ExpectMaxAgeAsync("connectors/file.js", TimeSpan.FromDays(14));
await ExpectMaxAgeAsync("scripts/file.js", TimeSpan.FromDays(14));
await ExpectMaxAgeAsync("images/file.avif", TimeSpan.FromDays(7));
response = await client.GetAsync("app-id.json", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
async Task ExpectMaxAgeAsync(string path, TimeSpan maxAge)
{
response = await client.GetAsync(path);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Headers.CacheControl);
Assert.Equal(maxAge, response.Headers.CacheControl.MaxAge);
}
}
private class TempDir([CallerMemberName] string test = null!) : IDisposable
{
public DirectoryInfo Info { get; } = Directory.CreateTempSubdirectory(test);
public void Dispose()
{
Info.Delete(recursive: true);
}
public async Task WriteAsync(string fileName, string content)
{
var fullPath = Path.Join(Info.FullName, fileName);
var directory = Path.GetDirectoryName(fullPath);
if (directory != null)
{
Directory.CreateDirectory(directory);
}
await File.WriteAllTextAsync(fullPath, content, TestContext.Current.CancellationToken);
}
}
}

View File

@@ -6,6 +6,13 @@ namespace Bit.Server;
public class Program
{
public static void Main(string[] args)
{
var builder = CreateWebHostBuilder(args);
var host = builder.Build();
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
@@ -37,7 +44,6 @@ public class Program
builder.UseWebRoot(webRoot);
}
var host = builder.Build();
host.Run();
return builder;
}
}