Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Data.SqlClient;

namespace Bit.Infrastructure.IntegrationTest.AdminConsole;

Expand All @@ -16,7 +18,7 @@ public static class OrganizationTestHelpers
{
public static Task<User> CreateTestUserAsync(this IUserRepository userRepository, string identifier = "test")
{
var id = Guid.NewGuid();
var id = CoreHelpers.GenerateComb();
return userRepository.CreateAsync(new User
{
Id = id,
Expand All @@ -34,7 +36,7 @@ public static Task<Organization> CreateTestOrganizationAsync(this IOrganizationR
int? seatCount = null,
string identifier = "test")
{
var id = Guid.NewGuid();
var id = CoreHelpers.GenerateComb();
return organizationRepository.CreateAsync(new Organization
{
Name = $"{identifier}-{id}",
Expand Down Expand Up @@ -181,6 +183,41 @@ public static Task<Collection> CreateTestCollectionAsync(
Name = $"{identifier} {Guid.NewGuid()}"
});

/// <summary>
/// Deletes an organization with retry logic for SQL Server deadlocks (error 1205).
/// Use this instead of <see cref="IOrganizationRepository.DeleteAsync"/> in test cleanup
/// to avoid deadlocks when tests run in parallel.
/// </summary>
public static async Task SafeDeleteAsync(this IOrganizationRepository repo, Organization org, int maxRetries = 3)
{
for (var attempt = 0; ; attempt++)
{
try
{
await repo.DeleteAsync(org);
return;
}
catch (Exception ex) when (attempt < maxRetries && IsDeadlock(ex))
{
await Task.Delay(Random.Shared.Next(50, 200));
}
}
}

private static bool IsDeadlock(Exception ex)
{
var current = ex;
while (current != null)
{
if (current is SqlException { Number: 1205 })
{
return true;
}
current = current.InnerException;
}
return false;
}

public static Task<OrganizationInviteLink> CreateTestOrganizationInviteLinkAsync(
this IOrganizationInviteLinkRepository repository,
Organization organization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ await collectionRepository.CreateAsync(collection,
// Clean up data
await userRepository.DeleteAsync(user1);
await userRepository.DeleteAsync(user2);
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await groupRepository.DeleteManyAsync([group1.Id, group2.Id]);
await organizationUserRepository.DeleteManyAsync([orgUser1.Id, orgUser2.Id]);
}
Expand Down Expand Up @@ -100,6 +100,6 @@ public async Task CreateAsync_WithNoAccess_Works(
Assert.Empty(actualAccess.Users);

// Clean up
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ await collectionRepository.ReplaceAsync(collection,
await userRepository.DeleteAsync(user1);
await userRepository.DeleteAsync(user2);
await userRepository.DeleteAsync(user3);
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
}

/// <remarks>
Expand Down Expand Up @@ -144,7 +144,7 @@ await collectionRepository.CreateAsync(collection,

// Clean up
await userRepository.DeleteAsync(user);
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
}

[Theory, DatabaseData]
Expand Down Expand Up @@ -209,6 +209,6 @@ await collectionRepository.CreateAsync(collection,
// Clean up data
await userRepository.DeleteAsync(user1);
await userRepository.DeleteAsync(user2);
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private static async Task CleanupAsync(IOrganizationRepository organizationRepos
Organization organization,
IEnumerable<OrganizationUser> organizationUsers)
{
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);

await userRepository.DeleteManyAsync(
organizationUsers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
Assert.Contains(result, org => org.Id == organization2.Id);

// Clean up
await organizationRepository.DeleteAsync(organization1);
await organizationRepository.DeleteAsync(organization2);
await organizationRepository.SafeDeleteAsync(organization1);
await organizationRepository.SafeDeleteAsync(organization2);
}

[Theory, DatabaseData]
Expand Down Expand Up @@ -211,10 +211,10 @@
Assert.NotNull(updateResult);
Assert.Equal(organization.Id, updateResult.Id);
Assert.True(updateResult.SyncSeats);
Assert.Equal(requestDate.ToString("yyyy-MM-dd HH:mm:ss"), updateResult.RevisionDate.ToString("yyyy-MM-dd HH:mm:ss"));

Check warning on line 214 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.IncrementSeatCountAsync_GivenOrganizationHasNotChangedSeatCountBefore_WhenUpdatingOrgSeats_ThenSubscriptionUpdateIsSaved(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 214 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.IncrementSeatCountAsync_GivenOrganizationHasNotChangedSeatCountBefore_WhenUpdatingOrgSeats_ThenSubscriptionUpdateIsSaved(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

// Annul
await sutRepository.DeleteAsync(organization);
await sutRepository.SafeDeleteAsync(organization);
}

[DatabaseData, Theory]
Expand All @@ -236,10 +236,10 @@
Assert.NotNull(updateResult);
Assert.Equal(organization.Id, updateResult.Id);
Assert.True(updateResult.SyncSeats);
Assert.Equal(requestDate.ToString("yyyy-MM-dd HH:mm:ss"), updateResult.RevisionDate.ToString("yyyy-MM-dd HH:mm:ss"));

Check warning on line 239 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.IncrementSeatCountAsync_GivenOrganizationHasChangedSeatCountBeforeAndRecordExists_WhenUpdatingOrgSeats_ThenSubscriptionUpdateIsSaved(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 239 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.IncrementSeatCountAsync_GivenOrganizationHasChangedSeatCountBeforeAndRecordExists_WhenUpdatingOrgSeats_ThenSubscriptionUpdateIsSaved(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

// Annul
await sutRepository.DeleteAsync(organization);
await sutRepository.SafeDeleteAsync(organization);
}

[DatabaseData, Theory]
Expand All @@ -259,10 +259,10 @@
Assert.NotNull(updateResult);
Assert.Equal(organization.Id, updateResult.Id);
Assert.True(updateResult.SyncSeats);
Assert.Equal(requestDate.ToString("yyyy-MM-dd HH:mm:ss"), updateResult.RevisionDate.ToString("yyyy-MM-dd HH:mm:ss"));

Check warning on line 262 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.GetOrganizationsForSubscriptionSyncAsync_GivenOrganizationHasChangedSeatCount_WhenGettingOrgsToUpdate_ThenReturnsOrgSubscriptionUpdate(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 262 in test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / Run tests

The behavior of 'DateTime.ToString(string)' could vary based on the current user's locale settings. Replace this call in 'OrganizationRepositoryTests.GetOrganizationsForSubscriptionSyncAsync_GivenOrganizationHasChangedSeatCount_WhenGettingOrgsToUpdate_ThenReturnsOrgSubscriptionUpdate(IOrganizationRepository)' with a call to 'DateTime.ToString(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

// Annul
await sutRepository.DeleteAsync(organization);
await sutRepository.SafeDeleteAsync(organization);
}

[DatabaseData, Theory]
Expand All @@ -283,7 +283,7 @@
Assert.Null(result.FirstOrDefault(x => x.Id == organization.Id));

// Annul
await sutRepository.DeleteAsync(organization);
await sutRepository.SafeDeleteAsync(organization);
}

[DatabaseTheory, DatabaseData]
Expand Down Expand Up @@ -402,7 +402,7 @@
Assert.Equal(organization.UseMyItems, result.UseMyItems);

// Clean up
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
}

[Theory, DatabaseData]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task ReturnsDetails_WhenUserIsConfirmed(
Assert.Equal(OrganizationUserStatusType.Confirmed, result.Status);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand All @@ -54,7 +54,7 @@ public async Task ReturnsDetails_WhenUserIsAccepted(
Assert.Equal(OrganizationUserStatusType.Accepted, result.Status);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -82,8 +82,8 @@ public async Task ReturnsDetailsAcrossMultipleOrganizations_WhenUserIsConfirmedO
Assert.Contains(results, r => r.OrganizationId == acceptedOrg.Id && r.Status == OrganizationUserStatusType.Accepted);

// Annul
await organizationRepository.DeleteAsync(confirmedOrg);
await organizationRepository.DeleteAsync(acceptedOrg);
await organizationRepository.SafeDeleteAsync(confirmedOrg);
await organizationRepository.SafeDeleteAsync(acceptedOrg);
await userRepository.DeleteAsync(user);
}

Expand All @@ -105,7 +105,7 @@ public async Task DoesNotReturnDetails_WhenUserIsInvited(
Assert.DoesNotContain(results, r => r.OrganizationId == organization.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand All @@ -127,7 +127,7 @@ public async Task DoesNotReturnDetails_WhenUserIsRevoked(
Assert.DoesNotContain(results, r => r.OrganizationId == organization.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand All @@ -151,7 +151,7 @@ public async Task DoesNotReturnDetails_ForOtherUsers(
Assert.DoesNotContain(results, r => r.OrganizationId == organization.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteManyAsync([targetUser, otherUser]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ public async Task ConfirmOrganizationUserAsync_WhenUserIsAccepted_ReturnsTrue(IO
Assert.Equal(key, updatedUser.Key);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -1428,7 +1428,7 @@ public async Task ConfirmOrganizationUserAsync_WhenUserIsAlreadyConfirmed_Return
Assert.Equal(OrganizationUserStatusType.Confirmed, unchangedUser.Status);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -1462,7 +1462,7 @@ public async Task ConfirmOrganizationUserAsync_IsIdempotent_WhenCalledMultipleTi
Assert.Equal(OrganizationUserStatusType.Confirmed, finalUser.Status);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task ReturnsPolicies_WhenUserIsConfirmed(
Assert.Contains(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -64,7 +64,7 @@ public async Task ReturnsPolicies_WhenUserIsAccepted(
Assert.Contains(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -104,8 +104,8 @@ public async Task ReturnsPoliciesAcrossMultipleOrganizations_WhenUserIsConfirmed
Assert.Contains(results, p => p.Id == acceptedPolicy.Id);

// Annul
await organizationRepository.DeleteAsync(confirmedOrg);
await organizationRepository.DeleteAsync(acceptedOrg);
await organizationRepository.SafeDeleteAsync(confirmedOrg);
await organizationRepository.SafeDeleteAsync(acceptedOrg);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -141,7 +141,7 @@ await organizationUserRepository.CreateAsync(new OrganizationUser
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -170,7 +170,7 @@ public async Task DoesNotReturnPolicies_WhenUserIsRevoked(
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -201,7 +201,7 @@ public async Task DoesNotReturnPolicies_ForOtherUsers(
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteManyAsync([targetUser, otherUser]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task ShouldContainProviderData(
Assert.True(results.Single().IsProvider);

// Annul
await organizationRepository.DeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId });
await userRepository.DeleteAsync(user);

async Task ArrangeProvider()
Expand Down Expand Up @@ -92,8 +92,8 @@ public async Task ShouldNotReturnOtherOrganizations_WhenUserIsNotConnected(
Assert.DoesNotContain(results, result => result.OrganizationId == notConnectedOrg.Id);

// Annul
await organizationRepository.DeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.DeleteAsync(notConnectedOrg);
await organizationRepository.SafeDeleteAsync(new Organization { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.SafeDeleteAsync(notConnectedOrg);
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -126,7 +126,7 @@ public async Task ShouldOnlyReturnInputPolicyType(
Assert.DoesNotContain(results, result => result.PolicyType == notInputPolicyType);

// Annul
await organizationRepository.DeleteAsync(new Organization { Id = orgUser.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization { Id = orgUser.OrganizationId });
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -158,9 +158,9 @@ public async Task WhenDirectlyConnectedUserHasUserId_ShouldReturnOtherConnectedO
AssertPolicyDetailUserConnections(results, userOrgConnectedDirectly, userOrgConnectedByEmail, userOrgConnectedByUserId);

// Annul
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId });
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId });
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -188,9 +188,9 @@ public async Task WhenDirectlyConnectedUserHasEmail_ShouldReturnOtherConnectedOr
AssertPolicyDetailUserConnections(results, userOrgConnectedDirectly, userOrgConnectedByEmail, userOrgConnectedByUserId);

// Annul
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId });
await organizationRepository.DeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedDirectly.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedByEmail.OrganizationId });
await organizationRepository.SafeDeleteAsync(new Organization() { Id = userOrgConnectedByUserId.OrganizationId });
await userRepository.DeleteAsync(user);
}

Expand Down Expand Up @@ -227,7 +227,7 @@ public async Task ShouldReturnUserIds(
&& result.OrganizationId == orgUser2.OrganizationId);

// Annul
await organizationRepository.DeleteAsync(organization);
await organizationRepository.SafeDeleteAsync(organization);
await userRepository.DeleteManyAsync([user1, user2]);
}

Expand Down
Loading
Loading