diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/processor/ReferenceDataProcessor.java b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/processor/ReferenceDataProcessor.java index 71cf047cf1..d63e7723ee 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/processor/ReferenceDataProcessor.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/processor/ReferenceDataProcessor.java @@ -55,6 +55,17 @@ private void processSitesData(CSVSitesData sitesItem) { // ================================================== private void processChannelUserData(CSVChannelData channelDataItem) { String channelName = channelDataItem.getChannelName(); + String email = channelDataItem.getChannelUserEmail(); + + if (email == null || email.isBlank()) { + loggingService.logWarning( + "Skipping channel user entry with missing email for channel: %s, user: %s", + channelName, + channelDataItem.getChannelUser() + ); + return; + } + List existing = cacheService.getChannelReference(channelName) .orElse(new ArrayList<>()); diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReader.java b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReader.java index 03ad9a7204..cf72f05470 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReader.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReader.java @@ -111,6 +111,15 @@ public ItemReader createReader(boolean dryRun) { for (String[] user : matchedUsers) { String email = user[1]; + if (email == null || email.isBlank()) { + loggingService.logWarning( + "Skipping channel user with missing email for record %s, groupKey=%s, user=%s", + orig.getArchiveId(), + orig.getRecordingGroupKey(), + user[0] + ); + continue; + } String fullName = user[0]; String[] nameParts = fullName.split("\\."); String firstName = nameParts.length > 0 ? nameParts[0] : "Unknown"; diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationService.java b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationService.java index 02cf6218f6..e4cd1e7273 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationService.java @@ -281,6 +281,15 @@ public PostMigratedItemGroup createShareBookingAndInviteIfNotExists(BookingDTO b String email, String firstName, String lastName) { + if (email == null || email.isBlank()) { + loggingService.logWarning( + "Cannot create share booking: email is null or blank for user %s %s", + firstName, + lastName + ); + return null; + } + loggingService.logDebug("Preparing data for share booking and user: %s %s %s", email, firstName, lastName); String lowerEmail = email.toLowerCase(); diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReaderTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReaderTest.java index 99d4f5a38d..8db804282d 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReaderTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/reader/PostMigrationItemReaderTest.java @@ -543,6 +543,130 @@ private MigrationRecord migrationRecord(String archiveId, String groupKey) { return record; } + @Test + void createReaderSkipsUserWithNullEmail() throws Exception { + MigrationRecord record = migrationRecord("archive-null-email-user", "case|segment"); + record.setBookingId(UUID.randomUUID()); + + when(migrationRecordService.findShareableOrigs()).thenReturn(List.of(record)); + Map> channelMap = new HashMap<>(); + + channelMap.put("case|segment", Collections.singletonList(new String[] {"user.name", null})); + when(cacheService.getAllChannelReferences()).thenReturn(channelMap); + + BookingDTO booking = mock(BookingDTO.class); + when(booking.getShares()).thenReturn(List.of()); + when(bookingService.findById(record.getBookingId())).thenReturn(booking); + + var readerResult = reader.createReader(false); + assertThat(assertDoesNotThrow(readerResult::read)).isNull(); + + verify(loggingService).logWarning( + eq("Skipping channel user with missing email for record %s, groupKey=%s, user=%s"), + eq("archive-null-email-user"), + eq("case|segment"), + eq("user.name") + ); + verify(entityCreationService, never()).createShareBookingAndInviteIfNotExists( + any(), anyString(), anyString(), anyString() + ); + } + + @Test + void createReaderSkipsUserWithBlankEmail() throws Exception { + MigrationRecord record = migrationRecord("archive-blank-email-user", "case|segment"); + record.setBookingId(UUID.randomUUID()); + + when(migrationRecordService.findShareableOrigs()).thenReturn(List.of(record)); + Map> channelMap = new HashMap<>(); + + channelMap.put("case|segment", Collections.singletonList(new String[] {"user.name", " "})); + when(cacheService.getAllChannelReferences()).thenReturn(channelMap); + + BookingDTO booking = mock(BookingDTO.class); + when(booking.getShares()).thenReturn(List.of()); + when(bookingService.findById(record.getBookingId())).thenReturn(booking); + + var readerResult = reader.createReader(false); + assertThat(assertDoesNotThrow(readerResult::read)).isNull(); + + verify(loggingService).logWarning( + eq("Skipping channel user with missing email for record %s, groupKey=%s, user=%s"), + eq("archive-blank-email-user"), + eq("case|segment"), + eq("user.name") + ); + verify(entityCreationService, never()).createShareBookingAndInviteIfNotExists( + any(), anyString(), anyString(), anyString() + ); + } + + @Test + void createReaderSkipsUserWithEmptyEmail() throws Exception { + MigrationRecord record = migrationRecord("archive-empty-email-user", "case|segment"); + record.setBookingId(UUID.randomUUID()); + + when(migrationRecordService.findShareableOrigs()).thenReturn(List.of(record)); + Map> channelMap = new HashMap<>(); + + channelMap.put("case|segment", Collections.singletonList(new String[] {"user.name", ""})); + when(cacheService.getAllChannelReferences()).thenReturn(channelMap); + + BookingDTO booking = mock(BookingDTO.class); + when(booking.getShares()).thenReturn(List.of()); + when(bookingService.findById(record.getBookingId())).thenReturn(booking); + + var readerResult = reader.createReader(false); + assertThat(assertDoesNotThrow(readerResult::read)).isNull(); + + verify(loggingService).logWarning( + eq("Skipping channel user with missing email for record %s, groupKey=%s, user=%s"), + eq("archive-empty-email-user"), + eq("case|segment"), + eq("user.name") + ); + verify(entityCreationService, never()).createShareBookingAndInviteIfNotExists( + any(), anyString(), anyString(), anyString() + ); + } + + @Test + void createReaderProcessesValidUserWhenAnotherHasNullEmail() throws Exception { + MigrationRecord record = migrationRecord("archive-mixed-emails", "case|segment"); + record.setBookingId(UUID.randomUUID()); + + when(migrationRecordService.findShareableOrigs()).thenReturn(List.of(record)); + Map> channelMap = new HashMap<>(); + + channelMap.put("case|segment", Arrays.asList( + new String[] {"invalid.user", null}, + new String[] {"valid.user", "valid@example.com"} + )); + when(cacheService.getAllChannelReferences()).thenReturn(channelMap); + + BookingDTO booking = mock(BookingDTO.class); + when(booking.getShares()).thenReturn(List.of()); + when(bookingService.findById(record.getBookingId())).thenReturn(booking); + + PostMigratedItemGroup group = new PostMigratedItemGroup(); + when(entityCreationService.createShareBookingAndInviteIfNotExists( + booking, "valid@example.com", "valid", "user" + )).thenReturn(group); + + var readerResult = reader.createReader(false); + assertThat(assertDoesNotThrow(readerResult::read)).isEqualTo(group); + + verify(loggingService).logWarning( + eq("Skipping channel user with missing email for record %s, groupKey=%s, user=%s"), + eq("archive-mixed-emails"), + eq("case|segment"), + eq("invalid.user") + ); + verify(entityCreationService).createShareBookingAndInviteIfNotExists( + booking, "valid@example.com", "valid", "user" + ); + } + private BookingDTO buildBookingWithShares(String alreadySharedEmail) { ShareBookingDTO share = new ShareBookingDTO(); share.setId(UUID.randomUUID()); diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationServiceTest.java index fd6954ea13..594bd21e5b 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/batch/application/services/migration/EntityCreationServiceTest.java @@ -882,6 +882,72 @@ void createShareBookingAndInviteIfNotExistsReturnsNullWhenVodafoneMissing() { verify(loggingService).logWarning("Vodafone user ID not found in cache for email: %s", VODAFONE_EMAIL); } + @Test + @DisplayName("createShareBookingAndInviteIfNotExists should return null when email is null") + void createShareBookingAndInviteIfNotExistsReturnsNullWhenEmailIsNull() { + BookingDTO booking = createTestBooking(); + + PostMigratedItemGroup result = entityCreationService.createShareBookingAndInviteIfNotExists( + booking, + null, + "Test", + "User" + ); + + assertThat(result).isNull(); + verify(loggingService).logWarning( + eq("Cannot create share booking: email is null or blank for user %s %s"), + eq("Test"), + eq("User") + ); + verify(cacheService, never()).getHashValue(anyString(), anyString(), any()); + verify(cacheService, never()).getShareBooking(anyString()); + } + + @Test + @DisplayName("createShareBookingAndInviteIfNotExists should return null when email is blank") + void createShareBookingAndInviteIfNotExistsReturnsNullWhenEmailIsBlank() { + BookingDTO booking = createTestBooking(); + + PostMigratedItemGroup result = entityCreationService.createShareBookingAndInviteIfNotExists( + booking, + " ", + "Test", + "User" + ); + + assertThat(result).isNull(); + verify(loggingService).logWarning( + eq("Cannot create share booking: email is null or blank for user %s %s"), + eq("Test"), + eq("User") + ); + verify(cacheService, never()).getHashValue(anyString(), anyString(), any()); + verify(cacheService, never()).getShareBooking(anyString()); + } + + @Test + @DisplayName("createShareBookingAndInviteIfNotExists should return null when email is empty") + void createShareBookingAndInviteIfNotExistsReturnsNullWhenEmailIsEmpty() { + BookingDTO booking = createTestBooking(); + + PostMigratedItemGroup result = entityCreationService.createShareBookingAndInviteIfNotExists( + booking, + "", + "Test", + "User" + ); + + assertThat(result).isNull(); + verify(loggingService).logWarning( + eq("Cannot create share booking: email is null or blank for user %s %s"), + eq("Test"), + eq("User") + ); + verify(cacheService, never()).getHashValue(anyString(), anyString(), any()); + verify(cacheService, never()).getShareBooking(anyString()); + } + @Test @DisplayName("isOrigRecordingPersisted should identify persisted originals") void isOrigRecordingPersistedReturnsTrueWhenOrigHasRecordingId() throws Exception {