From ea25b50b455e5e6fb0a3cca47fb9cf628264a5c8 Mon Sep 17 00:00:00 2001 From: Adam Majava Date: Mon, 27 Apr 2026 19:23:40 +0200 Subject: [PATCH] feat: enhance activity log handling with improved error logging and transaction management --- .../service/ActivityLogService.java | 2 + .../service/CommentService.java | 10 ++++- .../service/impl/DocumentServiceImpl.java | 10 ++++- .../impl/SupportTicketServiceImpl.java | 10 ++++- .../service/DocumentServiceTest.java | 41 +++++++++++++++++++ .../service/SupportTicketServiceTest.java | 36 ++++++++++++++++ 6 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java index b80e3d9..b940fad 100644 --- a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java +++ b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.data.domain.Sort; import java.util.List; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service @@ -34,6 +35,7 @@ public ActivityLogService(ActivityLogRepository activityLogRepository, this.supportTicketRepository = supportTicketRepository; } + @Transactional(propagation = Propagation.REQUIRES_NEW) public ActivityLogDto createActivityLog(CreateActivityLogDto dto) { User user = userRepository.findById(dto.getUserId()) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + dto.getUserId())); diff --git a/src/main/java/org/group1/projectbackend/service/CommentService.java b/src/main/java/org/group1/projectbackend/service/CommentService.java index 2358f51..180aaff 100644 --- a/src/main/java/org/group1/projectbackend/service/CommentService.java +++ b/src/main/java/org/group1/projectbackend/service/CommentService.java @@ -16,10 +16,14 @@ import org.group1.projectbackend.repository.CommentRepository; import org.group1.projectbackend.repository.SupportTicketRepository; import org.group1.projectbackend.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Service public class CommentService { + private static final Logger logger = LoggerFactory.getLogger(CommentService.class); + private final SupportTicketRepository supportTicketRepository; private final CommentRepository commentRepository; private final CommentMapper commentMapper; @@ -118,7 +122,11 @@ private void logActivitySafely(ActivityType activityType, String description, Lo ticketId )); } catch (RuntimeException ex) { - System.err.println("Failed to create activity log: " + ex.getMessage()); + logger.error("Failed to create activity log for ticketId={} userId={} activityType={}", + ticketId, + userId, + activityType, + ex); } } } diff --git a/src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java b/src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java index 32886a7..34c231c 100644 --- a/src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java +++ b/src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java @@ -19,6 +19,8 @@ import org.group1.projectbackend.service.ActivityLogService; import org.group1.projectbackend.service.DocumentService; import org.group1.projectbackend.service.ObjectStorageService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,6 +32,8 @@ @Service public class DocumentServiceImpl implements DocumentService { + private static final Logger logger = LoggerFactory.getLogger(DocumentServiceImpl.class); + private final DocumentRepository documentRepository; private final SupportTicketRepository supportTicketRepository; private final UserRepository userRepository; @@ -202,7 +206,11 @@ private void logActivitySafely(ActivityType activityType, String description, Lo ticketId )); } catch (RuntimeException ex) { - System.err.println("Failed to create activity log: " + ex.getMessage()); + logger.error("Failed to create activity log for ticketId={} userId={} activityType={}", + ticketId, + userId, + activityType, + ex); } } } diff --git a/src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java b/src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java index 32cf3d1..1787260 100644 --- a/src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java +++ b/src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java @@ -14,11 +14,15 @@ import org.group1.projectbackend.repository.UserRepository; import org.group1.projectbackend.service.ActivityLogService; import org.group1.projectbackend.service.SupportTicketService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class SupportTicketServiceImpl implements SupportTicketService { + private static final Logger logger = LoggerFactory.getLogger(SupportTicketServiceImpl.class); + private final SupportTicketRepository supportTicketRepository; private final UserRepository userRepository; private final ActivityLogService activityLogService; @@ -108,7 +112,11 @@ private void logActivitySafely(ActivityType activityType, String description, Lo ticketId )); } catch (RuntimeException ex) { - System.err.println("Failed to create activity log: " + ex.getMessage()); + logger.error("Failed to create activity log for ticketId={} userId={} activityType={}", + ticketId, + userId, + activityType, + ex); } } } diff --git a/src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java b/src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java index 88e5ecf..e0ea055 100644 --- a/src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java +++ b/src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java @@ -150,6 +150,34 @@ void shouldDeleteUploadedObjectWhenDocumentSaveFails() { verify(objectStorageService).delete(anyString()); } + @Test + void shouldUploadDocumentEvenWhenActivityLogFails() { + MockMultipartFile file = new MockMultipartFile( + "file", + "guide.pdf", + "application/pdf", + "content".getBytes() + ); + + when(supportTicketRepository.findById(10L)).thenReturn(Optional.of(ticket)); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(s3StorageProperties.getBucket()).thenReturn("project-documents"); + when(documentRepository.save(any(Document.class))).thenAnswer(invocation -> { + Document savedDocument = invocation.getArgument(0); + savedDocument.setId(100L); + savedDocument.setCreatedAt(LocalDateTime.now()); + return savedDocument; + }); + when(activityLogService.createActivityLog(any())) + .thenThrow(new RuntimeException("Activity log failed")); + + DocumentResponse response = documentService.uploadDocument(10L, 1L, file); + + assertThat(response).isNotNull(); + assertThat(response.id()).isEqualTo(100L); + verify(documentRepository).save(any(Document.class)); + } + @Test void shouldListDocumentsForTicket() { when(supportTicketRepository.existsById(10L)).thenReturn(true); @@ -194,4 +222,17 @@ void shouldDeleteDocument() { && dto.getSupportTicketId().equals(10L) )); } + + @Test + void shouldDeleteDocumentEvenWhenActivityLogFails() { + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(user)); + when(documentRepository.findById(100L)).thenReturn(Optional.of(document)); + when(activityLogService.createActivityLog(any())) + .thenThrow(new RuntimeException("Activity log failed")); + + documentService.deleteDocument("testuser", 100L); + + verify(documentRepository).delete(document); + verify(objectStorageService).delete("tickets/10/test-guide.pdf"); + } } diff --git a/src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java b/src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java index 6d07787..3f044d2 100644 --- a/src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java +++ b/src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java @@ -92,6 +92,26 @@ void shouldCreateTicket() { )); } + @Test + void shouldCreateTicketEvenWhenActivityLogFails() { + CreateTicketRequest request = new CreateTicketRequest( + "VPN access issue", + "Cannot connect to the company VPN from home.", + TicketPriority.HIGH + ); + + when(userRepository.findByUsername("alice")).thenReturn(Optional.of(user)); + when(supportTicketRepository.save(any(SupportTicket.class))).thenReturn(ticket); + when(activityLogService.createActivityLog(any())) + .thenThrow(new RuntimeException("Activity log failed")); + + TicketResponse response = supportTicketService.createTicket("alice", request); + + assertThat(response).isNotNull(); + assertThat(response.id()).isEqualTo(10L); + verify(supportTicketRepository).save(any(SupportTicket.class)); + } + @Test void shouldGetTicketById() { when(supportTicketRepository.findById(10L)).thenReturn(Optional.of(ticket)); @@ -132,6 +152,22 @@ void shouldUpdateTicketStatus() { )); } + @Test + void shouldUpdateTicketStatusEvenWhenActivityLogFails() { + UpdateTicketStatusRequest request = new UpdateTicketStatusRequest(TicketStatus.RESOLVED); + when(userRepository.findByUsername("alice")).thenReturn(Optional.of(user)); + when(supportTicketRepository.findById(10L)).thenReturn(Optional.of(ticket)); + when(supportTicketRepository.save(ticket)).thenReturn(ticket); + when(activityLogService.createActivityLog(any())) + .thenThrow(new RuntimeException("Activity log failed")); + + TicketResponse response = supportTicketService.updateStatus("alice", 10L, request); + + assertThat(response).isNotNull(); + assertThat(response.status()).isEqualTo(TicketStatus.RESOLVED); + verify(supportTicketRepository).save(ticket); + } + @Test void shouldThrowWhenTicketIsNotFoundById() { when(supportTicketRepository.findById(999L)).thenReturn(Optional.empty());