Skip to content

Commit

Permalink
Add integration test for parallel db access in middleware (#1196)
Browse files Browse the repository at this point in the history
Make waiting for hg repos to be ready explicit

Add integration test for parallel db access in middleware
  • Loading branch information
myieye authored Nov 5, 2024
1 parent b5803ed commit 3ed4eb7
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 13 deletions.
4 changes: 3 additions & 1 deletion backend/Testing/ApiTests/ApiTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ApiTestBase
public string BaseUrl => TestingEnvironmentVariables.ServerBaseUrl;
private readonly SocketsHttpHandler _httpClientHandler;
public readonly HttpClient HttpClient;
public string? CurrJwt { get; private set; }

public ApiTestBase()
{
Expand Down Expand Up @@ -48,7 +49,8 @@ public virtual async Task<string> LoginAs(string user, string? password = null)
{
password ??= TestingEnvironmentVariables.DefaultPassword;
var response = await JwtHelper.ExecuteLogin(new SendReceiveAuth(user, password), HttpClient);
return JwtHelper.GetJwtFromLoginResponse(response);
CurrJwt = JwtHelper.GetJwtFromLoginResponse(response);
return CurrJwt;
}

public void ClearCookies()
Expand Down
74 changes: 74 additions & 0 deletions backend/Testing/ApiTests/GqlMiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Text.Json.Nodes;
using LexCore.Entities;
using Shouldly;
using Testing.Fixtures;
using static Testing.Services.Utils;

namespace Testing.ApiTests;

[Trait("Category", "Integration")]
public class GqlMiddlewareTests : IClassFixture<IntegrationFixture>
{
private readonly IntegrationFixture _fixture;
private readonly ApiTestBase _adminApiTester;

public GqlMiddlewareTests(IntegrationFixture fixture)
{
_fixture = fixture;
_adminApiTester = _fixture.AdminApiTester;
}

private async Task<JsonObject> QueryMyProjectsWithMembers()
{
var json = await _adminApiTester.ExecuteGql(
$$"""
query loadMyProjects {
myProjects(orderBy: [ { name: ASC } ]) {
code
id
name
users {
id
userId
role
}
}
}
""");
return json;
}

[Fact]
public async Task CanTriggerMultipleInstancesOfMiddlewareThatAccessDbSimultaneously()
{
var config1 = GetNewProjectConfig();
var config2 = GetNewProjectConfig();
var config3 = GetNewProjectConfig();

var projects = await Task.WhenAll(
RegisterProjectInLexBox(config1, _adminApiTester),
RegisterProjectInLexBox(config2, _adminApiTester),
RegisterProjectInLexBox(config3, _adminApiTester));

await using var project1 = projects[0];
await using var project2 = projects[1];
await using var project3 = projects[2];

await Task.WhenAll(
AddMemberToProject(config1, _adminApiTester, "editor", ProjectRole.Editor),
AddMemberToProject(config2, _adminApiTester, "editor", ProjectRole.Editor),
AddMemberToProject(config3, _adminApiTester, "editor", ProjectRole.Editor));

await _adminApiTester.LoginAs("editor");
// Because we assigned ProjectRole.Editor and these projects are new,
// our middlware will query the project confidentiality from the DB to determine
// if the user is allowed to view all members
var json = await QueryMyProjectsWithMembers();

json.ShouldNotBeNull();
var myProjects = json["data"]!["myProjects"]!.AsArray();
var ids = myProjects.Select(p => p!["id"]!.GetValue<Guid>());

projects.Select(p => p.id).ShouldBeSubsetOf(ids);
}
}
6 changes: 3 additions & 3 deletions backend/Testing/ApiTests/ResetProjectRaceConditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public async Task SimultaneousResetsDontResultIn404s()
var config3 = GetNewProjectConfig();

var projects = await Task.WhenAll(
RegisterProjectInLexBox(config1, _adminApiTester),
RegisterProjectInLexBox(config2, _adminApiTester),
RegisterProjectInLexBox(config3, _adminApiTester)
RegisterProjectInLexBox(config1, _adminApiTester, true),
RegisterProjectInLexBox(config2, _adminApiTester, true),
RegisterProjectInLexBox(config3, _adminApiTester, true)
);

await using var project1 = projects[0];
Expand Down
46 changes: 41 additions & 5 deletions backend/Testing/Services/Utils.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using LexCore.Entities;
using Quartz.Util;
using Shouldly;
using Testing.ApiTests;
Expand Down Expand Up @@ -33,7 +34,8 @@ public static ProjectConfig GetNewProjectConfig(HgProtocol? protocol = null, boo

public static async Task<LexboxProject> RegisterProjectInLexBox(
ProjectConfig config,
ApiTestBase apiTester
ApiTestBase apiTester,
bool waitForRepoReady = false
)
{
await apiTester.ExecuteGql($$"""
Expand Down Expand Up @@ -62,10 +64,42 @@ ... on DbError {
}
}
""");
await WaitForHgRefreshIntervalAsync();
if (waitForRepoReady) await WaitForHgRefreshIntervalAsync();
return new LexboxProject(apiTester, config.Id);
}

public static async Task AddMemberToProject(
ProjectConfig config,
ApiTestBase apiTester,
string usernameOrEmail,
ProjectRole role
)
{
await apiTester.ExecuteGql($$"""
mutation {
addProjectMember(input: {
projectId: "{{config.Id}}",
usernameOrEmail: "{{usernameOrEmail}}"
role: {{role.ToString().ToUpper()}}
canInvite: false
}) {
project {
id
}
errors {
__typename
... on Error {
message
}
... on InvalidEmailError {
address
}
}
}
}
""");
}

public static void ValidateSendReceiveOutput(string srOutput)
{
srOutput.ShouldNotContain("abort");
Expand Down Expand Up @@ -101,18 +135,20 @@ private static string GetNewProjectDir(string projectCode,

public record LexboxProject : IAsyncDisposable
{
public readonly Guid id;
private readonly ApiTestBase _apiTester;
private readonly Guid _id;
private readonly string _jwt;

public LexboxProject(ApiTestBase apiTester, Guid id)
{
this.id = id;
_apiTester = apiTester;
_id = id;
_jwt = apiTester.CurrJwt ?? throw new InvalidOperationException("No JWT found");
}

public async ValueTask DisposeAsync()
{
var response = await _apiTester.HttpClient.DeleteAsync($"api/project/{_id}");
var response = await _apiTester.HttpClient.DeleteAsync($"api/project/{id}?jwt={_jwt}");
response.EnsureSuccessStatusCode();
}
}
Expand Down
8 changes: 4 additions & 4 deletions backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task CloneConfidentialProjectAsOrgManager(HgProtocol protocol)
{
// Create a fresh project
var projectConfig = _srFixture.InitLocalFlexProjectWithRepo(protocol, isConfidential: true, LexData.SeedingData.TestOrgId);
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester);
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester, true);

// Push the project to the server
var sendReceiveParams = new SendReceiveParams(protocol, projectConfig);
Expand Down Expand Up @@ -97,7 +97,7 @@ public async Task ModifyProjectData(HgProtocol protocol)
{
// Create a fresh project
var projectConfig = _srFixture.InitLocalFlexProjectWithRepo();
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester);
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester, true);

// Push the project to the server
var sendReceiveParams = new SendReceiveParams(protocol, projectConfig);
Expand Down Expand Up @@ -127,7 +127,7 @@ public async Task SendReceiveAfterProjectReset(HgProtocol protocol)
{
// Create a fresh project
var projectConfig = _srFixture.InitLocalFlexProjectWithRepo(protocol, isConfidential: false, null, "SR_AfterReset");
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester);
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester, true);

var sendReceiveParams = new SendReceiveParams(protocol, projectConfig);
var srResult = _sendReceiveService.SendReceiveProject(sendReceiveParams, AdminAuth);
Expand Down Expand Up @@ -194,7 +194,7 @@ public async Task SendNewProject_Medium()
private async Task SendNewProject(int totalSizeMb, int fileCount)
{
var projectConfig = _srFixture.InitLocalFlexProjectWithRepo();
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester);
await using var project = await RegisterProjectInLexBox(projectConfig, _adminApiTester, true);

await WaitForHgRefreshIntervalAsync();

Expand Down

0 comments on commit 3ed4eb7

Please sign in to comment.