From 83478f9c698411da1d4ea539a0a3303775530f4d Mon Sep 17 00:00:00 2001
From: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Date: Fri, 23 May 2025 11:27:37 +1000
Subject: [PATCH] [PM-13274] [Unified] Add integration tests for creating and
updating collections (#5814)
---
.../AdminConsole/OrganizationTestHelpers.cs | 11 +-
.../CollectionRepositoryCreateTests.cs | 105 +++++++++++++
.../CollectionRepositoryReplaceTests.cs | 147 ++++++++++++++++++
.../CollectionRepositoryTests.cs | 145 +----------------
4 files changed, 263 insertions(+), 145 deletions(-)
create mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryCreateTests.cs
create mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryReplaceTests.cs
rename test/Infrastructure.IntegrationTest/{Vault/Repositories => AdminConsole/Repositories/CollectionRepository}/CollectionRepositoryTests.cs (76%)
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs
index e631280bb3..144bff9dcb 100644
--- a/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs
@@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
@@ -26,15 +27,23 @@ public static class OrganizationTestHelpers
});
}
+ ///
+ /// Creates an Enterprise organization.
+ ///
public static Task CreateTestOrganizationAsync(this IOrganizationRepository organizationRepository,
string identifier = "test")
=> organizationRepository.CreateAsync(new Organization
{
Name = $"{identifier}-{Guid.NewGuid()}",
BillingEmail = "billing@example.com", // TODO: EF does not enforce this being NOT NULL
- Plan = "Test", // TODO: EF does not enforce this being NOT NULl
+ Plan = "Enterprise (Annually)", // TODO: EF does not enforce this being NOT NULl
+ PlanType = PlanType.EnterpriseAnnually
});
+ ///
+ /// Creates a confirmed Owner for the specified organization and user.
+ /// Does not include any cryptographic material.
+ ///
public static Task CreateTestOrganizationUserAsync(
this IOrganizationUserRepository organizationUserRepository,
Organization organization,
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryCreateTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryCreateTests.cs
new file mode 100644
index 0000000000..8a51f201dc
--- /dev/null
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryCreateTests.cs
@@ -0,0 +1,105 @@
+using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.Entities;
+using Bit.Core.Models.Data;
+using Bit.Core.Repositories;
+using Xunit;
+
+namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
+
+public class CollectionRepositoryCreateTests
+{
+ [DatabaseTheory, DatabaseData]
+ public async Task CreateAsync_WithAccess_Works(
+ IUserRepository userRepository,
+ IOrganizationRepository organizationRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ IGroupRepository groupRepository,
+ ICollectionRepository collectionRepository)
+ {
+ // Arrange
+ var organization = await organizationRepository.CreateTestOrganizationAsync();
+
+ var user1 = await userRepository.CreateTestUserAsync();
+ var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user1);
+
+ var user2 = await userRepository.CreateTestUserAsync();
+ var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user2);
+
+ var group1 = await groupRepository.CreateTestGroupAsync(organization);
+ var group2 = await groupRepository.CreateTestGroupAsync(organization);
+
+ var collection = new Collection
+ {
+ Name = "Test Collection Name",
+ OrganizationId = organization.Id,
+ };
+
+ // Act
+ await collectionRepository.CreateAsync(collection,
+ [
+ new CollectionAccessSelection { Id = group1.Id, Manage = true, HidePasswords = true, ReadOnly = false, },
+ new CollectionAccessSelection { Id = group2.Id, Manage = false, HidePasswords = false, ReadOnly = true, },
+ ],
+ [
+ new CollectionAccessSelection { Id = orgUser1.Id, Manage = true, HidePasswords = false, ReadOnly = true },
+ new CollectionAccessSelection { Id = orgUser2.Id, Manage = false, HidePasswords = true, ReadOnly = false },
+ ]
+ );
+
+ // Assert
+ var (actualCollection, actualAccess) = await collectionRepository.GetByIdWithAccessAsync(collection.Id);
+
+ Assert.NotNull(actualCollection);
+ Assert.Equal("Test Collection Name", actualCollection.Name);
+
+ var groups = actualAccess.Groups.ToArray();
+ Assert.Equal(2, groups.Length);
+ Assert.Single(groups, g => g.Id == group1.Id && g.Manage && g.HidePasswords && !g.ReadOnly);
+ Assert.Single(groups, g => g.Id == group2.Id && !g.Manage && !g.HidePasswords && g.ReadOnly);
+
+ var users = actualAccess.Users.ToArray();
+ Assert.Equal(2, users.Length);
+ Assert.Single(users, u => u.Id == orgUser1.Id && u.Manage && !u.HidePasswords && u.ReadOnly);
+ Assert.Single(users, u => u.Id == orgUser2.Id && !u.Manage && u.HidePasswords && !u.ReadOnly);
+
+ // Clean up data
+ await userRepository.DeleteAsync(user1);
+ await userRepository.DeleteAsync(user2);
+ await organizationRepository.DeleteAsync(organization);
+ await groupRepository.DeleteManyAsync([group1.Id, group2.Id]);
+ await organizationUserRepository.DeleteManyAsync([orgUser1.Id, orgUser2.Id]);
+ }
+
+ ///
+ /// Makes sure that the sproc handles empty sets.
+ ///
+ [DatabaseTheory, DatabaseData]
+ public async Task CreateAsync_WithNoAccess_Works(
+ IOrganizationRepository organizationRepository,
+ ICollectionRepository collectionRepository)
+ {
+ // Arrange
+ var organization = await organizationRepository.CreateTestOrganizationAsync();
+
+ var collection = new Collection
+ {
+ Name = "Test Collection Name",
+ OrganizationId = organization.Id,
+ };
+
+ // Act
+ await collectionRepository.CreateAsync(collection, [], []);
+
+ // Assert
+ var (actualCollection, actualAccess) = await collectionRepository.GetByIdWithAccessAsync(collection.Id);
+
+ Assert.NotNull(actualCollection);
+ Assert.Equal("Test Collection Name", actualCollection.Name);
+
+ Assert.Empty(actualAccess.Groups);
+ Assert.Empty(actualAccess.Users);
+
+ // Clean up
+ await organizationRepository.DeleteAsync(organization);
+ }
+}
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryReplaceTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryReplaceTests.cs
new file mode 100644
index 0000000000..df01276493
--- /dev/null
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryReplaceTests.cs
@@ -0,0 +1,147 @@
+using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.Entities;
+using Bit.Core.Models.Data;
+using Bit.Core.Repositories;
+using Xunit;
+
+namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
+
+public class CollectionRepositoryReplaceTests
+{
+ [DatabaseTheory, DatabaseData]
+ public async Task ReplaceAsync_WithAccess_Works(
+ IUserRepository userRepository,
+ IOrganizationRepository organizationRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ IGroupRepository groupRepository,
+ ICollectionRepository collectionRepository)
+ {
+ // Arrange
+ var organization = await organizationRepository.CreateTestOrganizationAsync();
+
+ var user1 = await userRepository.CreateTestUserAsync();
+ var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user1);
+
+ var user2 = await userRepository.CreateTestUserAsync();
+ var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user2);
+
+ var user3 = await userRepository.CreateTestUserAsync();
+ var orgUser3 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user3);
+
+ var group1 = await groupRepository.CreateTestGroupAsync(organization);
+ var group2 = await groupRepository.CreateTestGroupAsync(organization);
+ var group3 = await groupRepository.CreateTestGroupAsync(organization);
+
+ var collection = new Collection
+ {
+ Name = "Test Collection Name",
+ OrganizationId = organization.Id,
+ };
+
+ await collectionRepository.CreateAsync(collection,
+ [
+ new CollectionAccessSelection { Id = group1.Id, Manage = true, HidePasswords = true, ReadOnly = false, },
+ new CollectionAccessSelection { Id = group2.Id, Manage = false, HidePasswords = false, ReadOnly = true, },
+ ],
+ [
+ new CollectionAccessSelection { Id = orgUser1.Id, Manage = true, HidePasswords = false, ReadOnly = true },
+ new CollectionAccessSelection { Id = orgUser2.Id, Manage = false, HidePasswords = true, ReadOnly = false },
+ ]
+ );
+
+ // Act
+ collection.Name = "Updated Collection Name";
+
+ await collectionRepository.ReplaceAsync(collection,
+ [
+ // Delete group1
+ // Update group2:
+ new CollectionAccessSelection { Id = group2.Id, Manage = true, HidePasswords = true, ReadOnly = false, },
+ // Add group3:
+ new CollectionAccessSelection { Id = group3.Id, Manage = false, HidePasswords = false, ReadOnly = true, },
+ ],
+ [
+ // Delete orgUser1
+ // Update orgUser2:
+ new CollectionAccessSelection { Id = orgUser2.Id, Manage = false, HidePasswords = false, ReadOnly = true },
+ // Add orgUser3:
+ new CollectionAccessSelection { Id = orgUser3.Id, Manage = true, HidePasswords = false, ReadOnly = true },
+ ]
+ );
+
+ // Assert
+ var (actualCollection, actualAccess) = await collectionRepository.GetByIdWithAccessAsync(collection.Id);
+
+ Assert.NotNull(actualCollection);
+ Assert.Equal("Updated Collection Name", actualCollection.Name);
+
+ var groups = actualAccess.Groups.ToArray();
+ Assert.Equal(2, groups.Length);
+ Assert.Single(groups, g => g.Id == group2.Id && g.Manage && g.HidePasswords && !g.ReadOnly);
+ Assert.Single(groups, g => g.Id == group3.Id && !g.Manage && !g.HidePasswords && g.ReadOnly);
+
+ var users = actualAccess.Users.ToArray();
+
+ Assert.Equal(2, users.Length);
+ Assert.Single(users, u => u.Id == orgUser2.Id && !u.Manage && !u.HidePasswords && u.ReadOnly);
+ Assert.Single(users, u => u.Id == orgUser3.Id && u.Manage && !u.HidePasswords && u.ReadOnly);
+
+ // Clean up data
+ await userRepository.DeleteAsync(user1);
+ await userRepository.DeleteAsync(user2);
+ await userRepository.DeleteAsync(user3);
+ await organizationRepository.DeleteAsync(organization);
+ }
+
+ ///
+ /// Makes sure that the sproc handles empty sets.
+ ///
+ [DatabaseTheory, DatabaseData]
+ public async Task ReplaceAsync_WithNoAccess_Works(
+ IUserRepository userRepository,
+ IOrganizationRepository organizationRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ IGroupRepository groupRepository,
+ ICollectionRepository collectionRepository)
+ {
+ // Arrange
+ var organization = await organizationRepository.CreateTestOrganizationAsync();
+
+ var user = await userRepository.CreateTestUserAsync();
+ var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
+
+ var group = await groupRepository.CreateTestGroupAsync(organization);
+
+ var collection = new Collection
+ {
+ Name = "Test Collection Name",
+ OrganizationId = organization.Id,
+ };
+
+ await collectionRepository.CreateAsync(collection,
+ [
+ new CollectionAccessSelection { Id = group.Id, Manage = true, HidePasswords = false, ReadOnly = true },
+ ],
+ [
+ new CollectionAccessSelection { Id = orgUser.Id, Manage = true, HidePasswords = false, ReadOnly = true },
+ ]);
+
+ // Act
+ collection.Name = "Updated Collection Name";
+
+ await collectionRepository.ReplaceAsync(collection, [], []);
+
+ // Assert
+ var (actualCollection, actualAccess) = await collectionRepository.GetByIdWithAccessAsync(collection.Id);
+
+ Assert.NotNull(actualCollection);
+ Assert.Equal("Updated Collection Name", actualCollection.Name);
+
+ Assert.Empty(actualAccess.Groups);
+ Assert.Empty(actualAccess.Users);
+
+ // Clean up
+ await userRepository.DeleteAsync(user);
+ await organizationRepository.DeleteAsync(organization);
+ }
+}
diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/CollectionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryTests.cs
similarity index 76%
rename from test/Infrastructure.IntegrationTest/Vault/Repositories/CollectionRepositoryTests.cs
rename to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryTests.cs
index 268d46ef6b..b96998415d 100644
--- a/test/Infrastructure.IntegrationTest/Vault/Repositories/CollectionRepositoryTests.cs
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryTests.cs
@@ -7,7 +7,7 @@ using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Xunit;
-namespace Bit.Infrastructure.IntegrationTest.Repositories;
+namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
public class CollectionRepositoryTests
{
@@ -463,147 +463,4 @@ public class CollectionRepositoryTests
Assert.False(c3.Unmanaged);
});
}
-
- [DatabaseTheory, DatabaseData]
- public async Task ReplaceAsync_Works(
- IUserRepository userRepository,
- IOrganizationRepository organizationRepository,
- IOrganizationUserRepository organizationUserRepository,
- IGroupRepository groupRepository,
- ICollectionRepository collectionRepository)
- {
- var user = await userRepository.CreateAsync(new User
- {
- Name = "Test User",
- Email = $"test+{Guid.NewGuid()}@email.com",
- ApiKey = "TEST",
- SecurityStamp = "stamp",
- });
-
- var organization = await organizationRepository.CreateAsync(new Organization
- {
- Name = "Test Org",
- PlanType = PlanType.EnterpriseAnnually,
- Plan = "Test Plan",
- BillingEmail = "billing@email.com"
- });
-
- var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
- {
- OrganizationId = organization.Id,
- UserId = user.Id,
- Status = OrganizationUserStatusType.Confirmed,
- });
-
- var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
- {
- OrganizationId = organization.Id,
- UserId = user.Id,
- Status = OrganizationUserStatusType.Confirmed,
- });
-
- var orgUser3 = await organizationUserRepository.CreateAsync(new OrganizationUser
- {
- OrganizationId = organization.Id,
- UserId = user.Id,
- Status = OrganizationUserStatusType.Confirmed,
- });
-
- var group1 = await groupRepository.CreateAsync(new Group
- {
- Name = "Test Group #1",
- OrganizationId = organization.Id,
- });
-
- var group2 = await groupRepository.CreateAsync(new Group
- {
- Name = "Test Group #2",
- OrganizationId = organization.Id,
- });
-
- var group3 = await groupRepository.CreateAsync(new Group
- {
- Name = "Test Group #3",
- OrganizationId = organization.Id,
- });
-
- var collection = new Collection
- {
- Name = "Test Collection Name",
- OrganizationId = organization.Id,
- };
-
- await collectionRepository.CreateAsync(collection,
- [
- new CollectionAccessSelection { Id = group1.Id, Manage = true, HidePasswords = true, ReadOnly = false, },
- new CollectionAccessSelection { Id = group2.Id, Manage = false, HidePasswords = false, ReadOnly = true, },
- ],
- [
- new CollectionAccessSelection { Id = orgUser1.Id, Manage = true, HidePasswords = false, ReadOnly = true },
- new CollectionAccessSelection { Id = orgUser2.Id, Manage = false, HidePasswords = true, ReadOnly = false },
- ]
- );
-
- collection.Name = "Updated Collection Name";
-
- await collectionRepository.ReplaceAsync(collection,
- [
- // Should delete group1
- new CollectionAccessSelection { Id = group2.Id, Manage = true, HidePasswords = true, ReadOnly = false, },
- // Should add group3
- new CollectionAccessSelection { Id = group3.Id, Manage = false, HidePasswords = false, ReadOnly = true, },
- ],
- [
- // Should delete orgUser1
- new CollectionAccessSelection { Id = orgUser2.Id, Manage = false, HidePasswords = false, ReadOnly = true },
- // Should add orgUser3
- new CollectionAccessSelection { Id = orgUser3.Id, Manage = true, HidePasswords = false, ReadOnly = true },
- ]
- );
-
- // Assert it
- var info = await collectionRepository.GetByIdWithPermissionsAsync(collection.Id, user.Id, true);
-
- Assert.NotNull(info);
-
- Assert.Equal("Updated Collection Name", info.Name);
-
- var groups = info.Groups.ToArray();
-
- Assert.Equal(2, groups.Length);
-
- var actualGroup2 = Assert.Single(groups.Where(g => g.Id == group2.Id));
-
- Assert.True(actualGroup2.Manage);
- Assert.True(actualGroup2.HidePasswords);
- Assert.False(actualGroup2.ReadOnly);
-
- var actualGroup3 = Assert.Single(groups.Where(g => g.Id == group3.Id));
-
- Assert.False(actualGroup3.Manage);
- Assert.False(actualGroup3.HidePasswords);
- Assert.True(actualGroup3.ReadOnly);
-
- var users = info.Users.ToArray();
-
- Assert.Equal(2, users.Length);
-
- var actualOrgUser2 = Assert.Single(users.Where(u => u.Id == orgUser2.Id));
-
- Assert.False(actualOrgUser2.Manage);
- Assert.False(actualOrgUser2.HidePasswords);
- Assert.True(actualOrgUser2.ReadOnly);
-
- var actualOrgUser3 = Assert.Single(users.Where(u => u.Id == orgUser3.Id));
-
- Assert.True(actualOrgUser3.Manage);
- Assert.False(actualOrgUser3.HidePasswords);
- Assert.True(actualOrgUser3.ReadOnly);
-
- // Clean up data
- await userRepository.DeleteAsync(user);
- await organizationRepository.DeleteAsync(organization);
- await groupRepository.DeleteManyAsync([group1.Id, group2.Id, group3.Id]);
- await organizationUserRepository.DeleteManyAsync([orgUser1.Id, orgUser2.Id, orgUser3.Id]);
- }
}