Files
Rui Tomé 3dd72f6118 [PM-22450] Bump Collection.RevisionDate on edits and access changes (#7380)
* Fix UpdateCollectionCommand to set RevisionDate using TimeProvider and update corresponding tests. Adjust tests to verify correct RevisionDate assignment during collection updates.

* Enhance BulkAddCollectionAccessCommand to include revision date in access updates. Update ICollectionRepository and its implementations to accept revision date parameter. Modify stored procedure to update collection revision dates accordingly. Add tests to verify correct behavior of access creation and revision date updates.

* Update GroupRepository and stored procedures to bump RevisionDate for affected collections during group creation and updates. Enhance integration tests to verify that collection revision dates are correctly updated when groups are created or modified.

* Implement revision date updates for affected collections in OrganizationUserRepository and related stored procedures. Add integration tests to ensure revision dates are correctly bumped during organization user creation and updates.

* Update database migration script

* Update migration script summary

* Refactor OrganizationUserReplaceTests to create collection first

* Refactor stored procedures to use Common Table Expressions (CTEs) for updating RevisionDate of affected collections. This change improves readability and maintainability by consolidating the logic for identifying affected collections in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections procedures.

* Enhance OrganizationUser_CreateManyWithCollectionsAndGroups stored procedure to accept RevisionDate parameter for updating affected collections. Update OrganizationUserRepository to utilize the provided RevisionDate when available, ensuring accurate revision date management during organization user operations.

* Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to utilize temporary table for CollectionUser data insertion. This change improves performance and maintains consistency in updating RevisionDate for affected collections.

* Refactor OrganizationUserRepository to consistently use RevisionDate from created OrganizationUsers when updating affected collections. This change enhances the accuracy of revision date management across the repository.

* Refactor tests to ensure consistent handling of RevisionDate across Group and Collection repositories. Update assertions to compare RevisionDate directly, improving accuracy in revision date management during tests.

* Restore BOM in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections

* Refactor GroupRepository and OrganizationUserRepository to improve handling of RevisionDate. Updated collection filtering logic to use HashSet for efficiency and ensured that affected collections are filtered by OrganizationId, enhancing accuracy in revision date management.

* Bump migration script date

* Remove internal set from RevisionDate on Group and OrganizationUser

The Dapper repositories use a System.Text.Json serialize/deserialize
round-trip to build *WithCollections objects. System.Text.Json silently
skips properties with non-public setters, so RevisionDate was reverting
to DateTime.UtcNow instead of preserving the value set in C#.

* Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to improve the logic for updating RevisionDate. The update now uses INNER JOINs to ensure accurate filtering of collections based on OrganizationId and CollectionUser data, enhancing the precision of revision date management.

* Fix sprocs styling

* Added early return to OrganizationUserRepository.CreateManyAsync if the supplied parameter is empty
2026-04-10 07:27:27 +01:00

98 lines
4.1 KiB
C#

using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.OrganizationUserRepository;
public class OrganizationUserCreateTests
{
[DatabaseTheory, DatabaseData]
public async Task CreateAsync_WithCollections_CreatesAccessAndBumpsCollectionRevisionDate(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository)
{
var organization = await organizationRepository.CreateTestOrganizationAsync();
var collection = await collectionRepository.CreateTestCollectionAsync(organization);
var orgUser = new OrganizationUser
{
OrganizationId = organization.Id,
UserId = null,
Status = OrganizationUserStatusType.Invited,
Type = OrganizationUserType.User,
RevisionDate = DateTime.UtcNow.AddMinutes(10),
};
await organizationUserRepository.CreateAsync(orgUser, [
new CollectionAccessSelection { Id = collection.Id, Manage = true, HidePasswords = false, ReadOnly = false }
]);
await AssertOrgUserAndCollectionRevisionDate(
organizationUserRepository, collectionRepository,
orgUser, collection.Id, orgUser.RevisionDate);
}
[DatabaseTheory, DatabaseData]
public async Task CreateManyAsync_WithCollections_CreatesAccessAndBumpsCollectionRevisionDate(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository)
{
var organization = await organizationRepository.CreateTestOrganizationAsync();
var collection = await collectionRepository.CreateTestCollectionAsync(organization);
var orgUser = new OrganizationUser
{
Id = CoreHelpers.GenerateComb(),
OrganizationId = organization.Id,
UserId = null,
Email = $"invite-{Guid.NewGuid()}@example.com",
Status = OrganizationUserStatusType.Invited,
Type = OrganizationUserType.User,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow.AddMinutes(10),
};
await organizationUserRepository.CreateManyAsync([
new CreateOrganizationUser
{
OrganizationUser = orgUser,
Collections = [new CollectionAccessSelection { Id = collection.Id, Manage = true }],
Groups = [],
}
]);
await AssertOrgUserAndCollectionRevisionDate(
organizationUserRepository, collectionRepository,
orgUser, collection.Id, orgUser.RevisionDate);
}
private static async Task AssertOrgUserAndCollectionRevisionDate(
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
OrganizationUser expectedOrgUser,
Guid collectionId,
DateTime expectedCollectionRevisionDate)
{
var (actualOrgUser, actualCollections) = await organizationUserRepository.GetByIdWithCollectionsAsync(expectedOrgUser.Id);
Assert.NotNull(actualOrgUser);
Assert.Equal(expectedOrgUser.OrganizationId, actualOrgUser.OrganizationId);
Assert.Equal(expectedOrgUser.Email, actualOrgUser.Email);
Assert.Equal(expectedOrgUser.Status, actualOrgUser.Status);
Assert.Equal(expectedOrgUser.Type, actualOrgUser.Type);
var collectionAccess = Assert.Single(actualCollections);
Assert.Equal(collectionId, collectionAccess.Id);
Assert.True(collectionAccess.Manage);
var (actualCollection, _) = await collectionRepository.GetByIdWithAccessAsync(collectionId);
Assert.NotNull(actualCollection);
Assert.Equal(expectedCollectionRevisionDate, actualCollection.RevisionDate, TimeSpan.FromMilliseconds(10));
}
}