diff --git a/server/lombok.config b/server/lombok.config new file mode 100644 index 0000000..8f7e8aa --- /dev/null +++ b/server/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml index 720ef9a..491e4f3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -172,6 +172,12 @@ + + + **/com/studybuddies/server/services/** + **/com/studybuddies/server/web/mapper/MeetingMapperImpl.class + + diff --git a/server/src/main/java/com/studybuddies/server/domain/MeetingEntity.java b/server/src/main/java/com/studybuddies/server/domain/MeetingEntity.java index 72c1f99..053a3ac 100644 --- a/server/src/main/java/com/studybuddies/server/domain/MeetingEntity.java +++ b/server/src/main/java/com/studybuddies/server/domain/MeetingEntity.java @@ -31,7 +31,6 @@ public class MeetingEntity { String title; String description; - String links; @Column(nullable = false) LocalDateTime date_from; diff --git a/server/src/main/java/com/studybuddies/server/web/GlobalExceptionHandler.java b/server/src/main/java/com/studybuddies/server/web/GlobalExceptionHandler.java index 1624124..9e1a0ed 100644 --- a/server/src/main/java/com/studybuddies/server/web/GlobalExceptionHandler.java +++ b/server/src/main/java/com/studybuddies/server/web/GlobalExceptionHandler.java @@ -4,7 +4,6 @@ import com.studybuddies.server.web.mapper.exceptions.DateFormatException; import com.studybuddies.server.web.mapper.exceptions.EndDateAfterStartDateException; import com.studybuddies.server.web.mapper.exceptions.InvalidRepeatStringException; -import com.studybuddies.server.web.mapper.exceptions.TimeFormatException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -17,11 +16,6 @@ protected ResponseEntity handleDateFormatException() { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Wrong date format. Please use dd-MM-yyyy:HH:mm"); } - @ExceptionHandler(TimeFormatException.class) - protected ResponseEntity handleTimeFormatException() { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Minutes must be divisible by 15"); - } - @ExceptionHandler(InvalidRepeatStringException.class) protected ResponseEntity handleInvalidRepeatStringException() { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Not allowed. (daily, weekly, monthly, never)"); diff --git a/server/src/main/java/com/studybuddies/server/web/dto/MeetingChangeRequest.java b/server/src/main/java/com/studybuddies/server/web/dto/MeetingChangeRequest.java index 43e86b1..332d8f3 100644 --- a/server/src/main/java/com/studybuddies/server/web/dto/MeetingChangeRequest.java +++ b/server/src/main/java/com/studybuddies/server/web/dto/MeetingChangeRequest.java @@ -1,9 +1,13 @@ package com.studybuddies.server.web.dto; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter public class MeetingChangeRequest { public String title; public String description; - public String links; public String date_from; public String date_until; public String repeatable; diff --git a/server/src/main/java/com/studybuddies/server/web/dto/MeetingCreationRequest.java b/server/src/main/java/com/studybuddies/server/web/dto/MeetingCreationRequest.java index e59be09..1b1762d 100644 --- a/server/src/main/java/com/studybuddies/server/web/dto/MeetingCreationRequest.java +++ b/server/src/main/java/com/studybuddies/server/web/dto/MeetingCreationRequest.java @@ -8,7 +8,6 @@ public class MeetingCreationRequest { @NotBlank public String title; public String description; - public String links; @NotBlank public String date_from; @NotBlank diff --git a/server/src/main/java/com/studybuddies/server/web/dto/MeetingResponse.java b/server/src/main/java/com/studybuddies/server/web/dto/MeetingResponse.java index 9980760..038c4e5 100644 --- a/server/src/main/java/com/studybuddies/server/web/dto/MeetingResponse.java +++ b/server/src/main/java/com/studybuddies/server/web/dto/MeetingResponse.java @@ -3,10 +3,11 @@ import jakarta.validation.constraints.NotBlank; public class MeetingResponse { + @NotBlank + public Long id; @NotBlank public String title; public String description; - public String links; @NotBlank public String date_from; @NotBlank diff --git a/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapper.java b/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapper.java index 19cffa1..da14095 100644 --- a/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapper.java +++ b/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapper.java @@ -16,7 +16,6 @@ public interface MeetingMapper { // Mappings for MeetingCreationRequestToMeetingEntity @Mapping(source = "title", target = "title") @Mapping(source = "description", target = "description") - @Mapping(source = "links", target = "links") @Mapping(source = "place", target = "place") @Mapping(source = "date_from", target = "date_from", qualifiedByName = "stringToLocalDate") @Mapping(source = "date_until", target = "date_until", qualifiedByName = "stringToLocalDate") @@ -25,9 +24,9 @@ public interface MeetingMapper { MeetingEntity MeetingCreationRequestToMeetingEntity(MeetingCreationRequest meetingCreationRequest); // Mappings for MeetingChangeRequestToMeetingEntity + @Mapping(source = "id", target = "id") @Mapping(source = "title", target = "title") @Mapping(source = "description", target = "description") - @Mapping(source = "links", target = "links") @Mapping(source = "place", target = "place") @Mapping(source = "date_from", target = "date_from", qualifiedByName = "changeStringToLocalDate") @Mapping(source = "date_until", target = "date_until", qualifiedByName = "changeStringToLocalDate") @@ -40,10 +39,6 @@ default void validate(@MappingTarget MeetingEntity meetingEntity) { LocalDateTime start = meetingEntity.getDate_from(); LocalDateTime end = meetingEntity.getDate_until(); - if(start == null || end == null) { - return; - } - if(start.isAfter(end)) { throw new EndDateAfterStartDateException(""); } diff --git a/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapperUtils.java b/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapperUtils.java index e144de5..ffa54c8 100644 --- a/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapperUtils.java +++ b/server/src/main/java/com/studybuddies/server/web/mapper/MeetingMapperUtils.java @@ -3,7 +3,6 @@ import com.studybuddies.server.domain.Repeat; import com.studybuddies.server.web.mapper.exceptions.DateFormatException; import com.studybuddies.server.web.mapper.exceptions.InvalidRepeatStringException; -import com.studybuddies.server.web.mapper.exceptions.TimeFormatException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -52,15 +51,12 @@ private Repeat stringToRepeat(String repeatString) { } private LocalDateTime stringToLocalDateTime(String dateString) { - // only accept values in following format: dd-MM-yyyy:hh:mm while mm is divisible by 15 + // only accept values in following format: dd-MM-yyyy:hh:mm DateTimeFormatter format = DateTimeFormatter.ofPattern("dd-MM-yyyy:HH:mm"); LocalDateTime dueDate; try { dueDate = LocalDateTime.parse(dateString, format); - if(dueDate.getMinute() % 15 != 0) { - throw new TimeFormatException(""); - } } catch(DateTimeParseException e) { throw new DateFormatException(""); } diff --git a/server/src/main/java/com/studybuddies/server/web/mapper/exceptions/TimeFormatException.java b/server/src/main/java/com/studybuddies/server/web/mapper/exceptions/TimeFormatException.java deleted file mode 100644 index 8cd42aa..0000000 --- a/server/src/main/java/com/studybuddies/server/web/mapper/exceptions/TimeFormatException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.studybuddies.server.web.mapper.exceptions; - -import com.studybuddies.server.services.exceptions.StudyBuddiesException; - -public class TimeFormatException extends StudyBuddiesException { - - public TimeFormatException(String message) { - super(message); - } -} diff --git a/server/src/test/java/com/studybuddies/server/services/MeetingServiceTest.java b/server/src/test/java/com/studybuddies/server/services/MeetingServiceTest.java index e183456..229a1d3 100644 --- a/server/src/test/java/com/studybuddies/server/services/MeetingServiceTest.java +++ b/server/src/test/java/com/studybuddies/server/services/MeetingServiceTest.java @@ -1,20 +1,22 @@ package com.studybuddies.server.services; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.studybuddies.server.domain.MeetingEntity; -import com.studybuddies.server.domain.Repeat; import com.studybuddies.server.persistance.MeetingRepository; +import com.studybuddies.server.services.exceptions.MeetingNotFoundException; +import com.studybuddies.server.web.dto.MeetingChangeRequest; import com.studybuddies.server.web.dto.MeetingCreationRequest; import com.studybuddies.server.web.mapper.MeetingMapper; import com.studybuddies.server.web.mapper.exceptions.InvalidRepeatStringException; -import java.time.LocalDateTime; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -38,7 +40,6 @@ public void saveMeetingToDatabaseTest_invalidRepeatable_throwsException() { MeetingCreationRequest mockMeeting = new MeetingCreationRequest(); mockMeeting.setTitle("Invalid Repeatable Meeting"); mockMeeting.setDescription("Meeting with invalid repeatable value."); - mockMeeting.setLinks(""); mockMeeting.setPlace(""); mockMeeting.setRepeatable("invalid_value"); mockMeeting.setDate_from("23-11-2020:15:30"); @@ -56,4 +57,103 @@ public void saveMeetingToDatabaseTest_invalidRepeatable_throwsException() { // Verify that save was never called verify(meetingRepository, never()).save(any(MeetingEntity.class)); } + @Test + public void saveMeetingToDatabase_success() { + // given + MeetingCreationRequest mockMeeting = new MeetingCreationRequest(); + mockMeeting.setTitle("Valid Meeting"); + mockMeeting.setDescription(""); + mockMeeting.setPlace(""); + mockMeeting.setRepeatable("NEVER"); + mockMeeting.setDate_from("23-11-2020:15:30"); + mockMeeting.setDate_until("26-11-2020:15:30"); + + MeetingEntity mockMeetingEntity = new MeetingEntity(); + mockMeetingEntity.setId(1L); + + when(meetingMapper.MeetingCreationRequestToMeetingEntity(mockMeeting)).thenReturn(mockMeetingEntity); + + when(meetingRepository.save(any(MeetingEntity.class))).thenReturn(mockMeetingEntity); + + // when + Long meetingId = meetingService.saveMeetingToDatabase(mockMeeting); + + // then + assertNotNull(meetingId); + assertEquals(1L, meetingId); + + verify(meetingMapper).MeetingCreationRequestToMeetingEntity(mockMeeting); + verify(meetingRepository).save(mockMeetingEntity); +} + + @Test + public void changeMeetingInDatabaseTest_meetingNotFound_throwsException() { + // given + Long meetingId = 1L; + MeetingChangeRequest mockChangeRequest = new MeetingChangeRequest(); + when(meetingRepository.findById(meetingId)).thenReturn(Optional.empty()); + + // then + assertThrows(MeetingNotFoundException.class, () -> { + meetingService.changeMeetingInDatabase(meetingId, mockChangeRequest); + }); + + verify(meetingRepository, never()).save(any(MeetingEntity.class)); + } + + @Test + public void retrieveMeetingFromDatabaseTest_meetingNotFound_throwsException() { + // given + Long meetingId = 1L; + when(meetingRepository.findById(meetingId)).thenReturn(Optional.empty()); + + // then + assertThrows(MeetingNotFoundException.class, () -> { + meetingService.retrieveMeetingFromDatabase(meetingId); + }); + } + + @Test + public void deleteMeetingFromDatabaseTest_validId_deletesMeeting() { + // given + Long meetingId = 1L; + + // when + meetingService.deleteMeetingFromDatabase(meetingId); + + // then + verify(meetingRepository, times(1)).deleteById(meetingId); + } + @Test + public void changeMeetingInDatabaseTest_updatesOnlyNonNullFields() { + // given + Long meetingId = 1L; + MeetingEntity existingMeeting = new MeetingEntity(); + existingMeeting.setTitle("Old Title"); + existingMeeting.setDescription("Old Description"); + existingMeeting.setPlace("Old Place"); + + MeetingChangeRequest mockChangeRequest = new MeetingChangeRequest(); + mockChangeRequest.setTitle("New Title"); // Should be updated + mockChangeRequest.setDescription(null); // Should NOT be updated + mockChangeRequest.setPlace(null); // Should NOT be updated + + MeetingEntity changedMeeting = new MeetingEntity(); + changedMeeting.setTitle("New Title"); + changedMeeting.setDescription(null); + changedMeeting.setPlace(null); + + when(meetingRepository.findById(meetingId)).thenReturn(Optional.of(existingMeeting)); + when(meetingMapper.MeetingChangeRequestToMeetingEntity(mockChangeRequest)).thenReturn(changedMeeting); + + // when + meetingService.changeMeetingInDatabase(meetingId, mockChangeRequest); + + // then + assertEquals("New Title", existingMeeting.getTitle()); // Updated + assertEquals("Old Description", existingMeeting.getDescription()); // Not updated + assertEquals("Old Place", existingMeeting.getPlace()); // Not updated + + verify(meetingRepository, times(1)).save(existingMeeting); + } } diff --git a/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperTest.java b/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperTest.java new file mode 100644 index 0000000..d821f17 --- /dev/null +++ b/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperTest.java @@ -0,0 +1,51 @@ +package com.studybuddies.server.web.mapper; + +import static org.junit.jupiter.api.Assertions.*; + +import com.studybuddies.server.domain.MeetingEntity; +import com.studybuddies.server.web.mapper.exceptions.EndDateAfterStartDateException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mapstruct.factory.Mappers; + +import java.time.LocalDateTime; + +@ExtendWith(MockitoExtension.class) +class MeetingMapperTest { + + private MeetingMapper meetingMapper; + + // Mockito cant work with interfaces apparently + @BeforeEach + void setUp() { + meetingMapper = Mappers.getMapper(MeetingMapper.class); + } + + @Test + void validate_shouldThrowException_whenStartDateIsAfterEndDate() { + // given + MeetingEntity meetingEntity = new MeetingEntity(); + meetingEntity.setDate_from(LocalDateTime.of(2024, 2, 10, 10, 0)); + meetingEntity.setDate_until(LocalDateTime.of(2024, 2, 9, 10, 0)); // End date before start date + + // when then + EndDateAfterStartDateException exception = assertThrows( + EndDateAfterStartDateException.class, + () -> meetingMapper.validate(meetingEntity) + ); + assertNotNull(exception); + } + + @Test + void validate_shouldNotThrowException_whenStartDateIsBeforeEndDate() { + // Arrange + MeetingEntity meetingEntity = new MeetingEntity(); + meetingEntity.setDate_from(LocalDateTime.of(2024, 2, 9, 10, 0)); + meetingEntity.setDate_until(LocalDateTime.of(2024, 2, 10, 10, 0)); + + // Act & Assert + assertDoesNotThrow(() -> meetingMapper.validate(meetingEntity)); + } +} diff --git a/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperUtilsTest.java b/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperUtilsTest.java new file mode 100644 index 0000000..bab21a4 --- /dev/null +++ b/server/src/test/java/com/studybuddies/server/web/mapper/MeetingMapperUtilsTest.java @@ -0,0 +1,115 @@ +package com.studybuddies.server.web.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.studybuddies.server.web.mapper.exceptions.DateFormatException; +import java.time.LocalDateTime; + +import com.studybuddies.server.domain.Repeat; +import com.studybuddies.server.web.mapper.exceptions.InvalidRepeatStringException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MeetingMapperUtilsTest { + + @InjectMocks + private MeetingMapperUtils meetingMapperUtils; + + @Test + void stringToLocalDate_shouldConvertValidStringToLocalDateTime() { + // Given + String validDate = "10-02-2024:10:00"; + + // When + LocalDateTime result = meetingMapperUtils.stringToLocalDate(validDate); + + // Then + assertNotNull(result); + assertEquals(LocalDateTime.of(2024, 2, 10, 10, 0), result); + } + + @Test + void stringToLocalDate_shouldThrowException_whenInvalidFormat() { + // Given + String invalidDate = "invalid-date"; + + // When & Then + assertThrows(DateFormatException.class, () -> meetingMapperUtils.stringToLocalDate(invalidDate)); + } + + @Test + void changeStringToLocalDate_shouldReturnNull_whenInputIsNullOrEmpty() { + // Given + String emptyDate = ""; + String nullDate = null; + + // When & Then + assertNull(meetingMapperUtils.changeStringToLocalDate(emptyDate)); + assertNull(meetingMapperUtils.changeStringToLocalDate(nullDate)); + } + + @Test + void changeStringToLocalDate_shouldConvertValidStringToLocalDateTime() { + // Given + String validDate = "10-02-2024:10:00"; + + // When + LocalDateTime result = meetingMapperUtils.changeStringToLocalDate(validDate); + + // Then + assertNotNull(result); + assertEquals(LocalDateTime.of(2024, 2, 10, 10, 0), result); + } + + @Test + void stringToRepeatEnum_shouldConvertValidStringToEnum() { + // Given + String repeatString = "DAILY"; + + // When + Repeat result = meetingMapperUtils.stringToRepeatEnum(repeatString); + + // Then + assertNotNull(result); + assertEquals(Repeat.DAILY, result); + } + + @Test + void stringToRepeatEnum_shouldThrowException_whenInvalidEnum() { + // Given + String invalidRepeat = "INVALID"; + + // When & Then + assertThrows(InvalidRepeatStringException.class, () -> meetingMapperUtils.stringToRepeatEnum(invalidRepeat)); + } + + @Test + void changeStringToRepeatEnum_shouldReturnNull_whenInputIsNullOrEmpty() { + // Given + String emptyRepeat = ""; + String nullRepeat = null; + + // When & Then + assertNull(meetingMapperUtils.changeStringToRepeatEnum(emptyRepeat)); + assertNull(meetingMapperUtils.changeStringToRepeatEnum(nullRepeat)); + } + + @Test + void changeStringToRepeatEnum_shouldConvertValidStringToEnum() { + // Given + String repeatString = "WEEKLY"; + + // When + Repeat result = meetingMapperUtils.changeStringToRepeatEnum(repeatString); + + // Then + assertNotNull(result); + assertEquals(Repeat.WEEKLY, result); + } +}