From 7a7b6d71467e6b360de911bcbafb1f691ed4fdf7 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 17 Jan 2024 08:17:46 +0100 Subject: [PATCH 1/4] fix(migration): add insert script to migration --- CHANGELOG.md | 5 +++++ src/Directory.Build.props | 2 +- .../Migrations/20240115113526_1.8.0-rc1.cs | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6828ecb035..d039efcb22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ New features, fixed bugs, known defects and other noteworthy changes to each release of the Catena-X Portal Backend. +## 1.8.0-RC1.1 + +### Bugfix +* fixed Database migration + ## 1.8.0-RC1 ### Change diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 843a6ca8d6..90907ef71f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,6 +20,6 @@ 1.8.0 - RC1 + RC1.1 diff --git a/src/portalbackend/PortalBackend.Migrations/Migrations/20240115113526_1.8.0-rc1.cs b/src/portalbackend/PortalBackend.Migrations/Migrations/20240115113526_1.8.0-rc1.cs index 24d15106eb..301712e93b 100644 --- a/src/portalbackend/PortalBackend.Migrations/Migrations/20240115113526_1.8.0-rc1.cs +++ b/src/portalbackend/PortalBackend.Migrations/Migrations/20240115113526_1.8.0-rc1.cs @@ -174,6 +174,8 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "id", onDelete: ReferentialAction.Cascade); + migrationBuilder.Sql("INSERT INTO portal.use_cases (id, name, shortname) values ('b3948771-3372-4568-9e0e-acca4e674098', 'Behavior Twin', 'BT') ON CONFLICT DO NOTHING"); + migrationBuilder.Sql("UPDATE portal.verified_credential_type_assigned_use_cases SET use_case_id = 'b3948771-3372-4568-9e0e-acca4e674098' WHERE verified_credential_type_id = 3 and use_case_id = 'c065a349-f649-47f8-94d5-1a504a855419'"); migrationBuilder.Sql("UPDATE portal.agreements SET agreement_status_id = 2 WHERE id = 'aa0a0000-7fbc-1f2f-817f-bce0502c1090'"); @@ -196,6 +198,10 @@ INNER JOIN portal.company_roles as cr on (aacr.company_role_id = cr.id) /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.Sql("DELETE FROM portal.verified_credential_type_assigned_use_cases WHERE use_case_id = 'c065a349-f649-47f8-94d5-1a504a855419'"); + migrationBuilder.Sql("DELETE FROM portal.verified_credential_type_assigned_external_types WHERE verified_credential_type_id = 6 and verified_credential_external_type_id = 6"); + migrationBuilder.Sql("DELETE FROM portal.verified_credential_type_assigned_kinds WHERE verified_credential_type_id = 6 and verified_credential_type_kind_id = 1"); + migrationBuilder.Sql("DELETE FROM portal.verified_credential_external_type_use_case_detail_versions WHERE verified_credential_external_type_id = 6"); migrationBuilder.Sql("UPDATE portal.verified_credential_type_assigned_use_cases SET use_case_id = 'c065a349-f649-47f8-94d5-1a504a855419' WHERE verified_credential_type_id = 3 and use_case_id = 'b3948771-3372-4568-9e0e-acca4e674098'"); migrationBuilder.Sql(@"DROP VIEW IF EXISTS portal.agreement_view"); From ec587407714b274194e9cde67d13c2a7d97ef5bf Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 18 Jan 2024 09:17:04 +0100 Subject: [PATCH 2/4] fix(notification): fix error 'Sequence contains more than one element' (#417) --- .../Repositories/NotificationRepository.cs | 4 +- .../NotificationRepositoryTests.cs | 199 ++++++++++++------ .../Seeder/Data/notifications.test.json | 24 ++- 3 files changed, 163 insertions(+), 64 deletions(-) diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/NotificationRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/NotificationRepository.cs index 1294112dfb..c634568e81 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/NotificationRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/NotificationRepository.cs @@ -79,7 +79,7 @@ public Notification DeleteNotification(Guid notificationId) => _dbContext.Notifications.AsNoTracking() .Where(notification => notification.ReceiverUserId == receiverUserId && - semantic == SearchSemanticTypeId.AND + (semantic == SearchSemanticTypeId.AND ? ((!isRead.HasValue || notification.IsRead == isRead.Value) && (!typeId.HasValue || notification.NotificationTypeId == typeId.Value) && (!topicId.HasValue || notification.NotificationType!.NotificationTypeAssignedTopic!.NotificationTopicId == topicId.Value) && @@ -93,7 +93,7 @@ public Notification DeleteNotification(Guid notificationId) => (onlyDueDate && notification.DueDate.HasValue) || (doneState.HasValue && notification.Done == doneState.Value) || (searchTypeIds.Any() && searchTypeIds.Contains(notification.NotificationTypeId)) || - (searchQuery != null && notification.Content != null && EF.Functions.ILike(notification.Content, $"%{searchQuery.EscapeForILike()}%")))) + (searchQuery != null && notification.Content != null && EF.Functions.ILike(notification.Content, $"%{searchQuery.EscapeForILike()}%"))))) .GroupBy(notification => notification.ReceiverUserId), sorting switch { diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/NotificationRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/NotificationRepositoryTests.cs index d0bc606708..01c17b4999 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/NotificationRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/NotificationRepositoryTests.cs @@ -188,158 +188,223 @@ public async Task DeleteNotification_WithExistingNotification_RemovesNotificatio #region GetAllAsDetailsByUserIdUntracked - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 6)] + [InlineData(SearchSemanticTypeId.OR, 0)] + public async Task GetAllAsDetailsByUserIdUntracked_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, null, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, null, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert - results.Should().NotBeNull(); - results!.Count.Should().Be(6); - results.Data.Count().Should().Be(6); - results.Data.Should().AllBeOfType(); + if (count == 0) + { + results.Should().BeNull(); + } + else + { + results.Should().NotBeNull(); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); + results.Data.Should().AllBeOfType(); + } } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_SortedByDateAsc_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 6)] + [InlineData(SearchSemanticTypeId.OR, 0)] + public async Task GetAllAsDetailsByUserIdUntracked_SortedByDateAsc_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, NotificationSorting.DateAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, NotificationSorting.DateAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert - results.Should().NotBeNull(); - results!.Data.Count().Should().Be(6); - results.Data.Should().BeInAscendingOrder(detailData => detailData.Created); + if (count == 0) + { + results.Should().BeNull(); + } + else + { + results.Should().NotBeNull(); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); + results.Data.Should().BeInAscendingOrder(detailData => detailData.Created); + } } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_SortedByDateDesc_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 6)] + [InlineData(SearchSemanticTypeId.OR, 0)] + public async Task GetAllAsDetailsByUserIdUntracked_SortedByDateDesc_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, NotificationSorting.DateDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, NotificationSorting.DateDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert - results.Should().NotBeNull(); - results!.Data.Count().Should().Be(6); - results.Data.Should().BeInDescendingOrder(detailData => detailData.Created); + if (count == 0) + { + results.Should().BeNull(); + } + else + { + results.Should().NotBeNull(); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); + results.Data.Should().BeInDescendingOrder(detailData => detailData.Created); + } } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_SortedByReadStatusAsc_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 6)] + [InlineData(SearchSemanticTypeId.OR, 0)] + public async Task GetAllAsDetailsByUserIdUntracked_SortedByReadStatusAsc_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert - results.Should().NotBeNull(); - results!.Data.Count().Should().Be(6); - results.Data.Should().BeInAscendingOrder(detailData => detailData.IsRead); + if (count == 0) + { + results.Should().BeNull(); + } + else + { + results.Should().NotBeNull(); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); + results.Data.Should().BeInAscendingOrder(detailData => detailData.IsRead); + } } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_SortedByReadStatusDesc_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 6)] + [InlineData(SearchSemanticTypeId.OR, 0)] + public async Task GetAllAsDetailsByUserIdUntracked_SortedByReadStatusDesc_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, NotificationSorting.ReadStatusDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, NotificationSorting.ReadStatusDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert - results.Should().NotBeNull(); - results!.Data.Count().Should().Be(6); - results.Data.Should().BeInDescendingOrder(detailData => detailData.IsRead); + if (count == 0) + { + results.Should().BeNull(); + } + else + { + results.Should().NotBeNull(); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); + results.Data.Should().BeInDescendingOrder(detailData => detailData.IsRead); + } } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithUnreadStatus_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 3)] + [InlineData(SearchSemanticTypeId.OR, 3)] + public async Task GetAllAsDetailsByUserIdUntracked_WithUnreadStatus_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act var results = await sut - .GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, false, null, null, false, null, null, Enumerable.Empty(), null)(0, 15) + .GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, false, null, null, false, null, null, Enumerable.Empty(), null)(0, 15) .ConfigureAwait(false); // Assert results.Should().NotBeNull(); - results!.Data.Count().Should().Be(3); + results!.Data.Count().Should().Be(count); results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => x.IsRead == false)); } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatus_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 3)] + [InlineData(SearchSemanticTypeId.OR, 3)] + public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatus_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act var results = await sut - .GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, true, null, null, false, null, null, Enumerable.Empty(), null)(0, 15) + .GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, true, null, null, false, null, null, Enumerable.Empty(), null)(0, 15) .ConfigureAwait(false); // Assert results.Should().NotBeNull(); - results!.Data.Count().Should().Be(3); + results!.Data.Count().Should().Be(count); results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => x.IsRead == true)); } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatusAndInfoType_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 1)] + [InlineData(SearchSemanticTypeId.OR, 3)] + public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatusAndInfoType_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, true, NotificationTypeId.INFO, null, false, NotificationSorting.ReadStatusDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, true, NotificationTypeId.INFO, null, false, NotificationSorting.ReadStatusDesc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert results.Should().NotBeNull(); - results!.Data.Count().Should().Be(1); - results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => x.IsRead == true && x.TypeId == NotificationTypeId.INFO)); + results!.Data.Count().Should().Be(count); + results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => + searchSemanticTypeId == SearchSemanticTypeId.AND + ? (x.IsRead == true && x.TypeId == NotificationTypeId.INFO) + : (x.IsRead == true || x.TypeId == NotificationTypeId.INFO))); } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatusAndActionType_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 1)] + [InlineData(SearchSemanticTypeId.OR, 3)] + public async Task GetAllAsDetailsByUserIdUntracked_WithReadStatusAndActionType_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, true, NotificationTypeId.ACTION, null, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, true, NotificationTypeId.ACTION, null, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert results.Should().NotBeNull(); - results!.Data.Count().Should().Be(1); - results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => x.IsRead == true && x.TypeId == NotificationTypeId.ACTION)); + results!.Data.Count().Should().Be(count); + results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => + searchSemanticTypeId == SearchSemanticTypeId.AND + ? (x.IsRead == true && x.TypeId == NotificationTypeId.ACTION) + : (x.IsRead == true || x.TypeId == NotificationTypeId.ACTION))); } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithTopic_ReturnsExpectedNotificationDetailData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 4)] + [InlineData(SearchSemanticTypeId.OR, 4)] + public async Task GetAllAsDetailsByUserIdUntracked_WithTopic_ReturnsExpectedNotificationDetailData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var sut = await CreateSut().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, NotificationTopicId.INFO, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, NotificationTopicId.INFO, false, NotificationSorting.ReadStatusAsc, null, Enumerable.Empty(), null)(0, 15).ConfigureAwait(false); // Assert results.Should().NotBeNull(); - results!.Data.Count().Should().Be(4); + results!.Data.Count().Should().Be(count); results.Data.Should().AllSatisfy(detailData => detailData.Should().Match(x => x.NotificationTopic == NotificationTopicId.INFO)); } @@ -386,15 +451,17 @@ public async Task GetAllAsDetailsByUserIdUntracked_WithUnlinkedNotificationTypeI await trans.RollbackAsync().ConfigureAwait(false); } - [Fact] - public async Task GetAllAsDetailsByUserIdUntracked_WithSearchParams_ReturnsExpectedData() + [Theory] + [InlineData(SearchSemanticTypeId.AND, 2)] + [InlineData(SearchSemanticTypeId.OR, 3)] + public async Task GetAllAsDetailsByUserIdUntracked_WithSearchParams_ReturnsExpectedData(SearchSemanticTypeId searchSemanticTypeId, int count) { // Arrange var (sut, context) = await CreateSutWithContext().ConfigureAwait(false); await context.SaveChangesAsync().ConfigureAwait(false); // Act - var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, SearchSemanticTypeId.AND, null, null, null, false, null, null, new[] + var results = await sut.GetAllNotificationDetailsByReceiver(_companyUserId, searchSemanticTypeId, null, null, null, false, null, null, new[] { NotificationTypeId.WELCOME_SERVICE_PROVIDER, NotificationTypeId.APP_RELEASE_REQUEST @@ -402,12 +469,22 @@ public async Task GetAllAsDetailsByUserIdUntracked_WithSearchParams_ReturnsExpec // Assert results.Should().NotBeNull(); - results!.Count.Should().Be(2); - results.Data.Count().Should().Be(2); + results!.Count.Should().Be(count); + results.Data.Count().Should().Be(count); results.Data.Should().AllBeOfType(); - results.Data.Should().Satisfy( - x => x.TypeId == NotificationTypeId.WELCOME_SERVICE_PROVIDER, - x => x.TypeId == NotificationTypeId.APP_RELEASE_REQUEST); + if (searchSemanticTypeId == SearchSemanticTypeId.AND) + { + results.Data.Should().Satisfy( + x => x.TypeId == NotificationTypeId.WELCOME_SERVICE_PROVIDER && x.Content == """{"offerId":"0fc768e5-d4cf-4d3d-a0db-379efedd60f5","provider":"DNS"}""", + x => x.TypeId == NotificationTypeId.APP_RELEASE_REQUEST && x.Content == """{"offerId":"0fc768e5-d4cf-4d3d-a0db-379efedd60f5","RequestorCompanyName":"DNS"}"""); + } + else + { + results.Data.Should().Satisfy( + x => x.TypeId == NotificationTypeId.WELCOME_SERVICE_PROVIDER && x.Content == """{"offerId":"0fc768e5-d4cf-4d3d-a0db-379efedd60f5","provider":"DNS"}""", + x => x.TypeId == NotificationTypeId.APP_RELEASE_REQUEST && x.Content == """{"offerId":"0fc768e5-d4cf-4d3d-a0db-379efedd60f5","RequestorCompanyName":"DNS"}""", + x => x.TypeId == NotificationTypeId.ACTION && x.Content == """{"offerId":"deadbeef-dead-beef-dead-beefdeadbeef","provider":"DNS"}"""); + } } #endregion diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/notifications.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/notifications.test.json index 680357e8b0..09d56a92f9 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/notifications.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/notifications.test.json @@ -14,7 +14,7 @@ "id": "1450F1B2-FCE6-473E-865A-62D6DC0F5A5A", "receiver_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020001", "date_created": "2022-08-10 18:01:33.570000 +00:00", - "content": null, + "content": "{\"offerId\":\"deadbeef-dead-beef-dead-beefdeadbeef\",\"provider\":\"DNS\"}", "notification_type_id": 2, "is_read": true, "due_date": "2022-09-10 18:01:33.570000 +00:00", @@ -64,5 +64,27 @@ "due_date": "2022-09-10 18:01:33.570000 +00:00", "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020001", "done": null + }, + { + "id": "95cfc685-932b-4f26-90c1-c0c83b3efebf", + "receiver_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019994", + "date_created": "2022-08-10 18:01:33.570000 +00:00", + "content": null, + "notification_type_id": 1, + "is_read": true, + "due_date": "2022-09-10 18:01:33.570000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020001", + "done": true + }, + { + "id": "61ceb049-3a02-45e2-9652-bf73d9491212", + "receiver_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019994", + "date_created": "2022-08-10 18:01:33.570000 +00:00", + "content": null, + "notification_type_id": 2, + "is_read": true, + "due_date": "2022-09-10 18:01:33.570000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020001", + "done": true } ] From 31425b6b476f48cec4592facf7bbf435bdd17b8c Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Thu, 18 Jan 2024 10:57:37 +0100 Subject: [PATCH 3/4] feat(registration): allow same status update (#418) ignore update if status is unchanged in endpoint Put: /api/registration/application/{applicationId}/status --- .../RegistrationBusinessLogic.cs | 10 +- .../Controllers/RegistrationController.cs | 2 +- .../RegistrationBusinessLogicTest.cs | 248 ++++++++++++------ 3 files changed, 183 insertions(+), 77 deletions(-) diff --git a/src/registration/Registration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/RegistrationBusinessLogic.cs index 68aea8b234..183808616f 100644 --- a/src/registration/Registration.Service/BusinessLogic/RegistrationBusinessLogic.cs +++ b/src/registration/Registration.Service/BusinessLogic/RegistrationBusinessLogic.cs @@ -481,9 +481,13 @@ public async Task SetOwnCompanyApplicationStatusAsync(Guid applicationId, C throw new NotFoundException($"CompanyApplication {applicationId} not found"); } - ValidateCompanyApplicationStatus(applicationId, status, applicationUserData, applicationRepository, _dateTimeProvider); + if (applicationUserData.StatusId != status) + { + ValidateCompanyApplicationStatus(applicationId, status, applicationUserData, applicationRepository, _dateTimeProvider); + return await _portalRepositories.SaveAsync().ConfigureAwait(false); + } - return await _portalRepositories.SaveAsync().ConfigureAwait(false); + return 0; } public async Task GetOwnCompanyApplicationStatusAsync(Guid applicationId) @@ -820,7 +824,7 @@ private static void ValidateCompanyApplicationStatus(Guid applicationId, x => x.applicationStatus == applicationData.StatusId && x.status == status)) { throw new ArgumentException( - $"invalid status update requested {status}, current status is {applicationData.StatusId}, possible values are: {CompanyApplicationStatusId.SUBMITTED}"); + $"invalid status update requested {status}, current status is {applicationData.StatusId}, possible values are: {string.Join(",", allowedCombination.Where(x => x.applicationStatus == status).Select(x => x.applicationStatus))}"); } applicationRepository.AttachAndModifyCompanyApplication(applicationId, a => diff --git a/src/registration/Registration.Service/Controllers/RegistrationController.cs b/src/registration/Registration.Service/Controllers/RegistrationController.cs index 93dd8ba678..67fbe0d83e 100644 --- a/src/registration/Registration.Service/Controllers/RegistrationController.cs +++ b/src/registration/Registration.Service/Controllers/RegistrationController.cs @@ -191,7 +191,7 @@ public Task> GetApplicationsDeclineDa /// Id of the application which status should be set. /// The status that should be set /// - /// Example: Put: /api/registration/application/4f0146c6-32aa-4bb1-b844-df7e8babdcb4/status + /// Example: Put: /api/registration/application/{applicationId}/status /// Successfully set the status /// CompanyApplication was not found for the given id. /// Status must be null. diff --git a/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs b/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs index bf332b0538..9191547940 100644 --- a/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs +++ b/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs @@ -74,7 +74,11 @@ public class RegistrationBusinessLogicTest private readonly IOptions _options; private readonly IMailingService _mailingService; private readonly IStaticDataRepository _staticDataRepository; - private readonly Func _processLine; + + private readonly + Func + _processLine; + private readonly IIdentityService _identityService; private readonly IDateTimeProvider _dateTimeProvider; @@ -126,7 +130,9 @@ public RegistrationBusinessLogicTest() _alpha2code = "XY"; _error = _fixture.Create(); - _processLine = A.Fake>(); + _processLine = + A.Fake>(); SetupRepositories(); @@ -174,7 +180,9 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R var token = _fixture.Create(); var country = "XY"; - var uniqueIdSeed = _fixture.CreateMany<(BpdmIdentifierId BpdmIdentifierId, UniqueIdentifierId UniqueIdentifierId, string Value)>(5).ToImmutableArray(); + var uniqueIdSeed = _fixture + .CreateMany<(BpdmIdentifierId BpdmIdentifierId, UniqueIdentifierId UniqueIdentifierId, string Value)>(5) + .ToImmutableArray(); var name = _fixture.Create(); var shortName = _fixture.Create(); var region = _fixture.Create(); @@ -183,7 +191,8 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R var streetNumber = _fixture.Create(); var zipCode = _fixture.Create(); - var bpdmIdentifiers = uniqueIdSeed.Select(x => ((string TechnicalKey, string Value))(x.BpdmIdentifierId.ToString(), x.Value)); + var bpdmIdentifiers = uniqueIdSeed.Select(x => + ((string TechnicalKey, string Value))(x.BpdmIdentifierId.ToString(), x.Value)); var validIdentifiers = uniqueIdSeed.Skip(2).Take(2).Select(x => (x.BpdmIdentifierId, x.UniqueIdentifierId)); var bpdmAddress = _fixture.Build() @@ -191,10 +200,13 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R .With(x => x.Bpna, businessPartnerNumber) .With(x => x.PhysicalPostalAddress, _fixture.Build() .With(x => x.Country, _fixture.Build().With(x => x.TechnicalKey, country).Create()) - .With(x => x.AdministrativeAreaLevel1, _fixture.Build().With(x => x.RegionCode, region).Create()) + .With(x => x.AdministrativeAreaLevel1, + _fixture.Build().With(x => x.RegionCode, region).Create()) .With(x => x.PostalCode, zipCode) .With(x => x.City, city) - .With(x => x.Street, _fixture.Build().With(x => x.Name, streetName).With(x => x.HouseNumber, streetNumber).Create()) + .With(x => x.Street, + _fixture.Build().With(x => x.Name, streetName).With(x => x.HouseNumber, streetNumber) + .Create()) .Create()) .Create(); var legalEntity = _fixture.Build() @@ -202,14 +214,17 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R .With(x => x.LegalName, name) .With(x => x.LegalShortName, shortName) .With(x => x.Identifiers, bpdmIdentifiers.Select(identifier => _fixture.Build() - .With(x => x.Type, _fixture.Build().With(x => x.TechnicalKey, identifier.TechnicalKey).Create()) - .With(x => x.Value, identifier.Value) - .Create())) + .With(x => x.Type, + _fixture.Build().With(x => x.TechnicalKey, identifier.TechnicalKey).Create()) + .With(x => x.Value, identifier.Value) + .Create())) .With(x => x.LegalEntityAddress, bpdmAddress) .Create(); A.CallTo(() => bpnAccess.FetchLegalEntityByBpn(businessPartnerNumber, token, A._)) .Returns(legalEntity); - A.CallTo(() => _staticDataRepository.GetCountryAssignedIdentifiers(A>.That.Matches>(ids => ids.SequenceEqual(uniqueIdSeed.Select(seed => seed.BpdmIdentifierId))), country)) + A.CallTo(() => _staticDataRepository.GetCountryAssignedIdentifiers( + A>.That.Matches>(ids => + ids.SequenceEqual(uniqueIdSeed.Select(seed => seed.BpdmIdentifierId))), country)) .Returns((true, validIdentifiers)); var sut = new RegistrationBusinessLogic( @@ -224,18 +239,23 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R _dateTimeProvider); // Act - var result = await sut.GetCompanyBpdmDetailDataByBusinessPartnerNumber(businessPartnerNumber, token, CancellationToken.None).ConfigureAwait(false); + var result = await sut + .GetCompanyBpdmDetailDataByBusinessPartnerNumber(businessPartnerNumber, token, CancellationToken.None) + .ConfigureAwait(false); A.CallTo(() => bpnAccess.FetchLegalEntityByBpn(businessPartnerNumber, token, A._)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _staticDataRepository.GetCountryAssignedIdentifiers(A>.That.Matches>(ids => ids.SequenceEqual(uniqueIdSeed.Select(seed => seed.BpdmIdentifierId))), country)) + A.CallTo(() => _staticDataRepository.GetCountryAssignedIdentifiers( + A>.That.Matches>(ids => + ids.SequenceEqual(uniqueIdSeed.Select(seed => seed.BpdmIdentifierId))), country)) .MustHaveHappenedOnceExactly(); result.Should().NotBeNull(); result.BusinessPartnerNumber.Should().Be(businessPartnerNumber); result.CountryAlpha2Code.Should().Be(country); - var expectedUniqueIds = uniqueIdSeed.Skip(2).Take(2).Select(x => new CompanyUniqueIdData(x.UniqueIdentifierId, x.Value)); + var expectedUniqueIds = uniqueIdSeed.Skip(2).Take(2) + .Select(x => new CompanyUniqueIdData(x.UniqueIdentifierId, x.Value)); result.UniqueIds.Should().HaveSameCount(expectedUniqueIds); result.UniqueIds.Should().ContainInOrder(expectedUniqueIds); @@ -264,7 +284,9 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_T _dateTimeProvider); // Act - async Task Act() => await sut.GetCompanyBpdmDetailDataByBusinessPartnerNumber("NotLongEnough", "justatoken", CancellationToken.None).ConfigureAwait(false); + async Task Act() => + await sut.GetCompanyBpdmDetailDataByBusinessPartnerNumber("NotLongEnough", "justatoken", + CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -296,17 +318,25 @@ public async Task GetAllApplicationsForUserWithStatus_WithValidUser_GetsAllRoles _identityService, _dateTimeProvider); - var resultList = new[]{ + var resultList = new[] + { new CompanyApplicationWithStatus( _fixture.Create(), CompanyApplicationStatusId.VERIFY, - new[]{ - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.APPLICATION_ACTIVATION, ApplicationChecklistEntryStatusId.DONE), - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.DONE), - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.IN_PROGRESS), - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.FAILED), - new ApplicationChecklistData(ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO) + new[] + { + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.APPLICATION_ACTIVATION, + ApplicationChecklistEntryStatusId.DONE), + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, + ApplicationChecklistEntryStatusId.DONE), + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, + ApplicationChecklistEntryStatusId.DONE), + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, + ApplicationChecklistEntryStatusId.IN_PROGRESS), + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, + ApplicationChecklistEntryStatusId.FAILED), + new ApplicationChecklistData(ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, + ApplicationChecklistEntryStatusId.TO_DO) }) }; A.CallTo(() => _userRepository.GetApplicationsWithStatusUntrackedAsync(userCompanyId)) @@ -317,12 +347,18 @@ public async Task GetAllApplicationsForUserWithStatus_WithValidUser_GetsAllRoles result.Should().ContainSingle(); result.Single().ApplicationStatus.Should().Be(CompanyApplicationStatusId.VERIFY); result.Single().ApplicationChecklist.Should().NotBeNull().And.HaveCount(6).And.Satisfy( - x => x.TypeId == ApplicationChecklistEntryTypeId.APPLICATION_ACTIVATION && x.StatusId == ApplicationChecklistEntryStatusId.DONE, - x => x.TypeId == ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER && x.StatusId == ApplicationChecklistEntryStatusId.DONE, - x => x.TypeId == ApplicationChecklistEntryTypeId.CLEARING_HOUSE && x.StatusId == ApplicationChecklistEntryStatusId.DONE, - x => x.TypeId == ApplicationChecklistEntryTypeId.IDENTITY_WALLET && x.StatusId == ApplicationChecklistEntryStatusId.IN_PROGRESS, - x => x.TypeId == ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION && x.StatusId == ApplicationChecklistEntryStatusId.FAILED, - x => x.TypeId == ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP && x.StatusId == ApplicationChecklistEntryStatusId.TO_DO + x => x.TypeId == ApplicationChecklistEntryTypeId.APPLICATION_ACTIVATION && + x.StatusId == ApplicationChecklistEntryStatusId.DONE, + x => x.TypeId == ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER && + x.StatusId == ApplicationChecklistEntryStatusId.DONE, + x => x.TypeId == ApplicationChecklistEntryTypeId.CLEARING_HOUSE && + x.StatusId == ApplicationChecklistEntryStatusId.DONE, + x => x.TypeId == ApplicationChecklistEntryTypeId.IDENTITY_WALLET && + x.StatusId == ApplicationChecklistEntryStatusId.IN_PROGRESS, + x => x.TypeId == ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION && + x.StatusId == ApplicationChecklistEntryStatusId.FAILED, + x => x.TypeId == ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP && + x.StatusId == ApplicationChecklistEntryStatusId.TO_DO ); } @@ -350,7 +386,8 @@ public async Task GetCompanyWithAddressAsync_WithValidApplication_GetsData() _identityService, _dateTimeProvider); - A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) + A.CallTo(() => + _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) .Returns(data); // Act @@ -378,7 +415,8 @@ public async Task GetCompanyWithAddressAsync_WithInvalidApplication_ThrowsNotFou _identityService, _dateTimeProvider); - A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) + A.CallTo(() => + _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) .Returns((CompanyApplicationDetailData?)null); // Act @@ -405,7 +443,8 @@ public async Task GetCompanyWithAddressAsync_WithInvalidUser_ThrowsForbiddenExce _identityService, _dateTimeProvider); - A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) + A.CallTo(() => + _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, _identity.CompanyId, null)) .Returns(_fixture.Build().With(x => x.IsUserOfCompany, false).Create()); // Act @@ -425,9 +464,15 @@ public async Task GetCompanyWithAddressAsync_WithInvalidUser_ThrowsForbiddenExce [InlineData("filled", null, null, null, new UniqueIdentifierId[] { }, new string[] { }, "City")] [InlineData("filled", "filled", null, null, new UniqueIdentifierId[] { }, new string[] { }, "StreetName")] [InlineData("filled", "filled", "filled", "", new UniqueIdentifierId[] { }, new string[] { }, "CountryAlpha2Code")] - [InlineData("filled", "filled", "filled", "XX", new UniqueIdentifierId[] { UniqueIdentifierId.VAT_ID, UniqueIdentifierId.LEI_CODE }, new string[] { "filled", "" }, "UniqueIds")] - [InlineData("filled", "filled", "filled", "XX", new UniqueIdentifierId[] { UniqueIdentifierId.VAT_ID, UniqueIdentifierId.VAT_ID }, new string[] { "filled", "filled" }, "UniqueIds")] - public async Task SetCompanyWithAddressAsync_WithMissingData_ThrowsArgumentException(string? name, string? city, string? streetName, string? countryCode, IEnumerable uniqueIdentifierIds, IEnumerable values, string argumentName) + [InlineData("filled", "filled", "filled", "XX", + new UniqueIdentifierId[] { UniqueIdentifierId.VAT_ID, UniqueIdentifierId.LEI_CODE }, new string[] { "filled", "" }, + "UniqueIds")] + [InlineData("filled", "filled", "filled", "XX", + new UniqueIdentifierId[] { UniqueIdentifierId.VAT_ID, UniqueIdentifierId.VAT_ID }, + new string[] { "filled", "filled" }, "UniqueIds")] + public async Task SetCompanyWithAddressAsync_WithMissingData_ThrowsArgumentException(string? name, string? city, + string? streetName, string? countryCode, IEnumerable uniqueIdentifierIds, + IEnumerable values, string argumentName) { //Arrange var identityData = A.Fake(); @@ -447,7 +492,8 @@ public async Task SetCompanyWithAddressAsync_WithMissingData_ThrowsArgumentExcep _dateTimeProvider); var uniqueIdData = uniqueIdentifierIds.Zip(values, (id, value) => new CompanyUniqueIdData(id, value)); - var companyData = new CompanyDetailData(Guid.NewGuid(), name!, city!, streetName!, countryCode!, null, null, null, null, null, null, uniqueIdData); + var companyData = new CompanyDetailData(Guid.NewGuid(), name!, city!, streetName!, countryCode!, null, null, + null, null, null, null, uniqueIdData); // Act async Task Act() => await sut.SetCompanyDetailDataAsync(Guid.NewGuid(), companyData).ConfigureAwait(false); @@ -474,7 +520,8 @@ public async Task SetCompanyWithAddressAsync_WithInvalidApplicationId_ThrowsNotF _identityService, _dateTimeProvider); - var companyData = new CompanyDetailData(companyId, "name", "munich", "main street", "de", null, null, null, null, null, null, Enumerable.Empty()); + var companyData = new CompanyDetailData(companyId, "name", "munich", "main street", "de", null, null, null, + null, null, null, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, A._, companyId)) .ReturnsLazily(() => (CompanyApplicationDetailData?)null); @@ -498,7 +545,8 @@ public async Task SetCompanyWithAddressAsync_WithoutCompanyUserId_ThrowsForbidde A.CallTo(() => identityData.IdentityTypeId).Returns(IdentityTypeId.COMPANY_USER); A.CallTo(() => identityData.CompanyId).Returns(companyId); A.CallTo(() => _identityService.IdentityData).Returns(identityData); - var companyData = new CompanyDetailData(companyId, "name", "munich", "main street", "de", null, null, null, null, null, null, Enumerable.Empty()); + var companyData = new CompanyDetailData(companyId, "name", "munich", "main street", "de", null, null, null, + null, null, null, Enumerable.Empty()); var sut = new RegistrationBusinessLogic( _options, @@ -512,7 +560,8 @@ public async Task SetCompanyWithAddressAsync_WithoutCompanyUserId_ThrowsForbidde _dateTimeProvider); A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, A._, companyId)) - .ReturnsLazily(() => _fixture.Build().With(x => x.IsUserOfCompany, false).Create()); + .ReturnsLazily(() => + _fixture.Build().With(x => x.IsUserOfCompany, false).Create()); // Act async Task Act() => await sut.SetCompanyDetailDataAsync(applicationId, companyData).ConfigureAwait(false); @@ -555,7 +604,9 @@ public async Task SetCompanyWithAddressAsync__WithInvalidBpn_ThrowsControllerArg // Assert var ex = await Assert.ThrowsAsync(Act); - ex.Message.Should().Be("BPN must contain exactly 16 characters and must be prefixed with BPNL (Parameter 'BusinessPartnerNumber')"); + ex.Message.Should() + .Be( + "BPN must contain exactly 16 characters and must be prefixed with BPNL (Parameter 'BusinessPartnerNumber')"); } [Fact] @@ -751,7 +802,8 @@ public async Task SetCompanyWithAddressAsync_WithoutInitialCompanyAddress_Create // Assert A.CallTo(() => _companyRepository.CreateAddress(A._, A._, A._, A>._)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _companyRepository.CreateAddress(companyData.City, companyData.StreetName, companyData.CountryAlpha2Code, A>._)) + A.CallTo(() => _companyRepository.CreateAddress(companyData.City, companyData.StreetName, + companyData.CountryAlpha2Code, A>._)) .MustHaveHappened(); A.CallTo(() => _companyRepository.AttachAndModifyCompany(A._, A>._, A>._)) .MustHaveHappenedOnceExactly(); @@ -825,7 +877,9 @@ public async Task SetCompanyWithAddressAsync_WithInitialCompanyAddress_ModifyAdd modify(company); }); - A.CallTo(() => _companyRepository.AttachAndModifyAddress(existingData.AddressId!.Value, A>._, A>._)) + A.CallTo(() => + _companyRepository.AttachAndModifyAddress(existingData.AddressId!.Value, A>._, + A>._)) .Invokes((Guid addressId, Action
? initialize, Action
modify) => { address = new Address(addressId, null!, null!, null!, default); @@ -839,9 +893,12 @@ public async Task SetCompanyWithAddressAsync_WithInitialCompanyAddress_ModifyAdd // Assert A.CallTo(() => _companyRepository.CreateAddress(A._, A._, A._, A?>._)) .MustNotHaveHappened(); - A.CallTo(() => _companyRepository.AttachAndModifyAddress(A._, A>._, A>._!)) + A.CallTo( + () => _companyRepository.AttachAndModifyAddress(A._, A>._, A>._!)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _companyRepository.AttachAndModifyAddress(existingData.AddressId!.Value, A>._, A>._!)) + A.CallTo(() => + _companyRepository.AttachAndModifyAddress(existingData.AddressId!.Value, A>._, + A>._!)) .MustHaveHappened(); A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); @@ -877,9 +934,12 @@ public async Task SetCompanyWithAddressAsync_WithUniqueIdentifiers_CreateModifyD A.CallTo(() => _identityService.IdentityData).Returns(identityData); var uniqueIdentifiers = _fixture.CreateMany(4); - var firstIdData = _fixture.Build().With(x => x.UniqueIdentifierId, uniqueIdentifiers.First()).Create(); // shall not modify - var secondIdData = _fixture.Build().With(x => x.UniqueIdentifierId, uniqueIdentifiers.ElementAt(1)).Create(); // shall modify - var thirdIdData = _fixture.Build().With(x => x.UniqueIdentifierId, uniqueIdentifiers.ElementAt(2)).Create(); // shall create new + var firstIdData = _fixture.Build() + .With(x => x.UniqueIdentifierId, uniqueIdentifiers.First()).Create(); // shall not modify + var secondIdData = _fixture.Build() + .With(x => x.UniqueIdentifierId, uniqueIdentifiers.ElementAt(1)).Create(); // shall modify + var thirdIdData = _fixture.Build() + .With(x => x.UniqueIdentifierId, uniqueIdentifiers.ElementAt(2)).Create(); // shall create new var companyData = _fixture.Build() .With(x => x.BusinessPartnerNumber, (string?)null) @@ -889,10 +949,12 @@ public async Task SetCompanyWithAddressAsync_WithUniqueIdentifiers_CreateModifyD .Create(); var existingData = _fixture.Build() - .With(x => x.UniqueIds, new[] { - (firstIdData.UniqueIdentifierId, firstIdData.Value), // shall be left unmodified - (secondIdData.UniqueIdentifierId, _fixture.Create()), // shall be modified - (uniqueIdentifiers.ElementAt(3), _fixture.Create()) }) // shall be deleted + .With(x => x.UniqueIds, new[] + { + (firstIdData.UniqueIdentifierId, firstIdData.Value), // shall be left unmodified + (secondIdData.UniqueIdentifierId, _fixture.Create()), // shall be modified + (uniqueIdentifiers.ElementAt(3), _fixture.Create()) + }) // shall be deleted .With(x => x.IsUserOfCompany, true) .Create(); var application = _fixture.Build() @@ -917,13 +979,17 @@ public async Task SetCompanyWithAddressAsync_WithUniqueIdentifiers_CreateModifyD A.CallTo(() => _applicationRepository.GetCompanyApplicationDetailDataAsync(applicationId, A._, companyId)) .Returns(existingData); - A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(A._, A>._, A>._)) - .Invokes((Guid _, IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> initial, IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> modified) => + A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(A._, + A>._, A>._)) + .Invokes((Guid _, IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> initial, + IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> modified) => { initialIdentifiers = initial; modifiedIdentifiers = modified; }); - A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._)) + A.CallTo(() => + _applicationRepository.AttachAndModifyCompanyApplication(applicationId, + A>._)) .Invokes((Guid _, Action setOptionalFields) => { setOptionalFields.Invoke(application); @@ -933,15 +999,23 @@ public async Task SetCompanyWithAddressAsync_WithUniqueIdentifiers_CreateModifyD await sut.SetCompanyDetailDataAsync(applicationId, companyData).ConfigureAwait(false); // Assert - A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(companyId, A>._, A>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(A.That.Not.IsEqualTo(companyId), A>._, A>._)).MustNotHaveHappened(); - A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(companyId, + A>._, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _companyRepository.CreateUpdateDeleteIdentifiers(A.That.Not.IsEqualTo(companyId), + A>._, A>._)) + .MustNotHaveHappened(); + A.CallTo(() => + _applicationRepository.AttachAndModifyCompanyApplication(applicationId, + A>._)) + .MustHaveHappenedOnceExactly(); A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); initialIdentifiers.Should().NotBeNull(); modifiedIdentifiers.Should().NotBeNull(); initialIdentifiers.Should().ContainInOrder(existingData.UniqueIds); - modifiedIdentifiers.Should().ContainInOrder((firstIdData.UniqueIdentifierId, firstIdData.Value), (secondIdData.UniqueIdentifierId, secondIdData.Value), (thirdIdData.UniqueIdentifierId, thirdIdData.Value)); + modifiedIdentifiers.Should().ContainInOrder((firstIdData.UniqueIdentifierId, firstIdData.Value), + (secondIdData.UniqueIdentifierId, secondIdData.Value), (thirdIdData.UniqueIdentifierId, thirdIdData.Value)); application.DateLastChanged.Should().Be(now); } @@ -970,7 +1044,8 @@ public async Task SetCompanyWithAddressAsync_WithInvalidCountryCode_Throws() _identityService, _dateTimeProvider); - A.CallTo(() => _countryRepository.GetCountryAssignedIdentifiers(A._, A>._)) + A.CallTo(() => + _countryRepository.GetCountryAssignedIdentifiers(A._, A>._)) .Returns((false, null!)); // Act @@ -995,7 +1070,9 @@ public async Task SetCompanyWithAddressAsync_WithInvalidUniqueIdentifiers_Throws var companyData = _fixture.Build() .With(x => x.BusinessPartnerNumber, (string?)null) .With(x => x.CountryAlpha2Code, _alpha2code) - .With(x => x.UniqueIds, identifiers.Select(id => _fixture.Build().With(x => x.UniqueIdentifierId, id).Create())) + .With(x => x.UniqueIds, + identifiers.Select(id => + _fixture.Build().With(x => x.UniqueIdentifierId, id).Create())) .Create(); var sut = new RegistrationBusinessLogic( @@ -1009,7 +1086,8 @@ public async Task SetCompanyWithAddressAsync_WithInvalidUniqueIdentifiers_Throws _identityService, _dateTimeProvider); - A.CallTo(() => _countryRepository.GetCountryAssignedIdentifiers(_alpha2code, A>._)) + A.CallTo(() => + _countryRepository.GetCountryAssignedIdentifiers(_alpha2code, A>._)) .Returns((true, new[] { identifiers.First() })); // Act @@ -1017,7 +1095,8 @@ public async Task SetCompanyWithAddressAsync_WithInvalidUniqueIdentifiers_Throws //Assert var result = await Assert.ThrowsAsync(Act).ConfigureAwait(false); - result.Message.Should().Be($"invalid uniqueIds for country {_alpha2code}: '{identifiers.ElementAt(1)}' (Parameter 'UniqueIds')"); + result.Message.Should() + .Be($"invalid uniqueIds for country {_alpha2code}: '{identifiers.ElementAt(1)}' (Parameter 'UniqueIds')"); } #endregion @@ -1078,7 +1157,9 @@ public async Task SetOwnCompanyApplicationStatusAsync_WithInvalidApplication_Thr .ReturnsLazily(() => new ValueTuple()); // Act - async Task Act() => await sut.SetOwnCompanyApplicationStatusAsync(applicationId, CompanyApplicationStatusId.VERIFY).ConfigureAwait(false); + async Task Act() => + await sut.SetOwnCompanyApplicationStatusAsync(applicationId, CompanyApplicationStatusId.VERIFY) + .ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1105,20 +1186,33 @@ public async Task SetOwnCompanyApplicationStatusAsync_WithInvalidStatus_ThrowsAr null!, _identityService, _dateTimeProvider); - + var existingStatus = CompanyApplicationStatusId.CREATED; A.CallTo(() => _applicationRepository.GetOwnCompanyApplicationUserDataAsync(A._, A._)) - .ReturnsLazily(() => new ValueTuple(true, CompanyApplicationStatusId.CREATED)); + .ReturnsLazily(() => new ValueTuple(true, existingStatus)); + var status = CompanyApplicationStatusId.VERIFY; // Act - async Task Act() => await sut.SetOwnCompanyApplicationStatusAsync(applicationId, CompanyApplicationStatusId.VERIFY).ConfigureAwait(false); + async Task Act() => await sut.SetOwnCompanyApplicationStatusAsync(applicationId, status).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); - ex.Message.Should().Contain("invalid status update requested"); + ex.Message.Should().Contain($"invalid status update requested {status}, current status is {existingStatus}, possible values are: {status}"); } - [Fact] - public async Task SetOwnCompanyApplicationStatusAsync_WithValidData_SavesChanges() + [Theory] + [InlineData(CompanyApplicationStatusId.CREATED, CompanyApplicationStatusId.CREATED, false)] + [InlineData(CompanyApplicationStatusId.CREATED, CompanyApplicationStatusId.ADD_COMPANY_DATA, true)] + [InlineData(CompanyApplicationStatusId.ADD_COMPANY_DATA, CompanyApplicationStatusId.ADD_COMPANY_DATA, false)] + [InlineData(CompanyApplicationStatusId.ADD_COMPANY_DATA, CompanyApplicationStatusId.INVITE_USER, true)] + [InlineData(CompanyApplicationStatusId.INVITE_USER, CompanyApplicationStatusId.INVITE_USER, false)] + [InlineData(CompanyApplicationStatusId.INVITE_USER, CompanyApplicationStatusId.SELECT_COMPANY_ROLE, true)] + [InlineData(CompanyApplicationStatusId.SELECT_COMPANY_ROLE, CompanyApplicationStatusId.SELECT_COMPANY_ROLE, false)] + [InlineData(CompanyApplicationStatusId.SELECT_COMPANY_ROLE, CompanyApplicationStatusId.UPLOAD_DOCUMENTS, true)] + [InlineData(CompanyApplicationStatusId.UPLOAD_DOCUMENTS, CompanyApplicationStatusId.UPLOAD_DOCUMENTS, false)] + [InlineData(CompanyApplicationStatusId.UPLOAD_DOCUMENTS, CompanyApplicationStatusId.VERIFY, true)] + [InlineData(CompanyApplicationStatusId.VERIFY, CompanyApplicationStatusId.VERIFY, false)] + [InlineData(CompanyApplicationStatusId.VERIFY, CompanyApplicationStatusId.SUBMITTED, true)] + public async Task SetOwnCompanyApplicationStatusAsync_WithValidData_SavesChanges(CompanyApplicationStatusId currentStatus, CompanyApplicationStatusId expectedStatus, bool shouldUpdate) { //Arrange var now = DateTimeOffset.Now; @@ -1149,15 +1243,23 @@ public async Task SetOwnCompanyApplicationStatusAsync_WithValidData_SavesChanges _dateTimeProvider); A.CallTo(() => _applicationRepository.GetOwnCompanyApplicationUserDataAsync(A._, A._)) - .ReturnsLazily(() => new ValueTuple(true, CompanyApplicationStatusId.VERIFY)); + .ReturnsLazily(() => new ValueTuple(true, currentStatus)); // Act - await sut.SetOwnCompanyApplicationStatusAsync(applicationId, CompanyApplicationStatusId.SUBMITTED).ConfigureAwait(false); + await sut.SetOwnCompanyApplicationStatusAsync(applicationId, expectedStatus).ConfigureAwait(false); // Assert - A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._)).MustHaveHappenedOnceExactly(); - application.DateLastChanged.Should().Be(now); + if (shouldUpdate) + { + A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._)).MustHaveHappenedOnceExactly(); + application.DateLastChanged.Should().Be(now); + application.ApplicationStatusId.Should().Be(expectedStatus); + } + else + { + A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._)).MustNotHaveHappened(); + } } #endregion From 761536a560c0721a5b6b3c5c0a39191054a1b749 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Thu, 18 Jan 2024 11:39:31 +0100 Subject: [PATCH 4/4] build: update changelog for v1.8.0-RC2 (#419) * update changelog for v1.8.0-RC2 * bump version for v1.8.0-RC2 --- CHANGELOG.md | 8 ++++++++ src/Directory.Build.props | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d039efcb22..c0f07b15f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ New features, fixed bugs, known defects and other noteworthy changes to each release of the Catena-X Portal Backend. +## 1.8.0-RC2 + +### Bugfix +* Notification Service + * fixed Get: /api/notification/ endpoint which resulted in a 'Sequence contains more than one element' error +* Registration Service + * fixed Put: /api/registration/application/{applicationId}/status endpoint to allow same status as existing status + ## 1.8.0-RC1.1 ### Bugfix diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 90907ef71f..0274e58d38 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,6 +20,6 @@ 1.8.0 - RC1.1 + RC2