using System.Text.Json; using Bit.Scim.IntegrationTest.Factories; using Bit.Scim.Models; using Bit.Scim.Utilities; using Bit.Test.Common.Helpers; using Xunit; namespace Bit.Scim.IntegrationTest.Controllers.v2; public class GroupsControllerPatchTests : IClassFixture, IAsyncLifetime { private readonly ScimApplicationFactory _factory; public GroupsControllerPatchTests(ScimApplicationFactory factory) { _factory = factory; } public Task InitializeAsync() { var databaseContext = _factory.GetDatabaseContext(); _factory.ReinitializeDbForTests(databaseContext); return Task.CompletedTask; } Task IAsyncLifetime.DisposeAsync() => Task.CompletedTask; [Fact] public async Task Patch_ReplaceDisplayName_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId1; var newDisplayName = "Patch Display Name"; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "replace", Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId); Assert.Equal(newDisplayName, group.Name); Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount, databaseContext.GroupUsers.Count()); Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1)); Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4)); } [Fact] public async Task Patch_ReplaceMembers_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId1; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "replace", Path = "members", Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}}]").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); Assert.Single(databaseContext.GroupUsers); Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count()); var groupUser = databaseContext.GroupUsers.FirstOrDefault(); Assert.Equal(ScimApplicationFactory.TestOrganizationUserId2, groupUser.OrganizationUserId); } [Fact] public async Task Patch_AddSingleMember_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId1; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "add", Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId2}\"]", Value = JsonDocument.Parse("{}").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount + 1, databaseContext.GroupUsers.Count()); Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1)); Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2)); Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4)); } [Fact] public async Task Patch_AddListMembers_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId2; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "add", Path = "members", Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}},{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId3}\"}}]").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2)); Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId3)); } [Fact] public async Task Patch_RemoveSingleMember_ReplaceDisplayName_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId1; var newDisplayName = "Patch Display Name"; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "remove", Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId1}\"]", Value = JsonDocument.Parse("{}").RootElement }, new ScimPatchModel.OperationModel { Op = "replace", Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count()); Assert.Equal(ScimApplicationFactory.InitialGroupCount, databaseContext.Groups.Count()); var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId); Assert.Equal(newDisplayName, group.Name); } [Fact] public async Task Patch_RemoveListMembers_Success() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = ScimApplicationFactory.TestGroupId1; var inputModel = new ScimPatchModel { Operations = new List() { new ScimPatchModel.OperationModel { Op = "remove", Path = "members", Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId1}\"}}, {{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId4}\"}}]").RootElement } }, Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); var databaseContext = _factory.GetDatabaseContext(); Assert.Empty(databaseContext.GroupUsers); } [Fact] public async Task Patch_NotFound() { var organizationId = ScimApplicationFactory.TestOrganizationId1; var groupId = Guid.NewGuid(); var inputModel = new Models.ScimPatchModel { Operations = new List(), Schemas = new List() { ScimConstants.Scim2SchemaGroup } }; var expectedResponse = new ScimErrorResponseModel { Status = StatusCodes.Status404NotFound, Detail = "Group not found.", Schemas = new List { ScimConstants.Scim2SchemaError } }; var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel); Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode); var responseModel = JsonSerializer.Deserialize(context.Response.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); AssertHelper.AssertPropertyEqual(expectedResponse, responseModel); } }