diff --git a/common/src/main/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplier.java b/common/src/main/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplier.java index 5d1468262c469c9595dabe39e5a7159a3f70a2de..510b7c92779fa39758cf3da4855a7af4dec20fe1 100644 --- a/common/src/main/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplier.java +++ b/common/src/main/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplier.java @@ -1,32 +1,26 @@ package de.ozgcloud.eingang.common.vorgang; -import java.time.Instant; import java.time.LocalDate; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; + @Component +@RequiredArgsConstructor public class VorgangNummerSupplier { - static final String BASE30_ALPHABET = "23456789ABCDEFGHJKMNPQRSTVWXYZ"; + static final String VORGANGNUMMER_TEMPLATE = "%d%X%02d-%s"; + static final char[] BASE30_ALPHABET = { '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', + 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; static final int SUFFIX_LENGTH = 6; public String get() { - var resultBuilder = initWithPrefix(); - long currentTimeSeconds = Instant.now().toEpochMilli(); - for (int i = 0; i < SUFFIX_LENGTH; i++) { - resultBuilder.append(BASE30_ALPHABET.charAt((int) (currentTimeSeconds % 30))); - currentTimeSeconds /= 30; - } - return resultBuilder.toString(); - } - - StringBuilder initWithPrefix() { var today = LocalDate.now(); var lastYearNumber = today.getYear() % 10; - var monthValue = "%02d".formatted(today.getMonthValue()); - var dayValue = "%02d".formatted(today.getDayOfMonth()); - return new StringBuilder().append(lastYearNumber).append(monthValue).append(dayValue).append("-"); + return VORGANGNUMMER_TEMPLATE.formatted(lastYearNumber, today.getMonthValue(), today.getDayOfMonth(), + RandomStringUtils.random(SUFFIX_LENGTH, BASE30_ALPHABET)); } } diff --git a/common/src/test/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplierTest.java b/common/src/test/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplierTest.java index d474cd190edfb39454d96c98a67d2d1a0886690d..2fc9a524f5e23d2bebd9721eb5612d96ad879782 100644 --- a/common/src/test/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplierTest.java +++ b/common/src/test/java/de/ozgcloud/eingang/common/vorgang/VorgangNummerSupplierTest.java @@ -4,12 +4,15 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import java.time.LocalDate; +import java.util.Random; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.Spy; +import org.springframework.test.util.ReflectionTestUtils; class VorgangNummerSupplierTest { @@ -17,71 +20,67 @@ class VorgangNummerSupplierTest { @InjectMocks private VorgangNummerSupplier vorgangNummerSupplier; - @Nested - @DisplayName("Get Vorgang Nummer") - class TestGetVorgangNummer { - @Test - void shouldCallAddPrefix() { - vorgangNummerSupplier.get(); - - verify(vorgangNummerSupplier).initWithPrefix(); - } + @Test + @DisplayName("should add random suffix of length 6") + void shouldAddSuffix() { + var result = vorgangNummerSupplier.get(); - @Test - @DisplayName("should add random suffix of length 6") - void shouldAddSuffix() { - doReturn(new StringBuilder()).when(vorgangNummerSupplier).initWithPrefix(); + assertThat(result).hasSize(11); + } - var result = vorgangNummerSupplier.get(); + @Test + void shouldCallGetRandomString() { + try (var randomStringUtils = mockStatic(RandomStringUtils.class)) { + vorgangNummerSupplier.get(); - assertThat(result).hasSize(6); + randomStringUtils.verify(() -> RandomStringUtils.random(VorgangNummerSupplier.SUFFIX_LENGTH, VorgangNummerSupplier.BASE30_ALPHABET)); } } - @Nested - class TestAddPrefix { + @Test + void shouldHaveSize() { + var result = vorgangNummerSupplier.get(); - @Test - void shouldHaveSize() { - var resultBuilder = vorgangNummerSupplier.initWithPrefix(); + assertThat(getPrefix(result)).hasSize(5); + } - assertThat(resultBuilder).hasSize(6); - } + private String getPrefix(String string) { + return string.substring(0, string.indexOf('-') + 1); + } - @Test - void shouldAddLastYearNumberFirst() { - var lastYearNumber = "" + LocalDate.now().getYear() % 10; + @Test + void shouldAddLastYearNumberFirst() { + var lastYearNumber = "" + LocalDate.now().getYear() % 10; - var resultBuilder = vorgangNummerSupplier.initWithPrefix(); + var result = vorgangNummerSupplier.get(); - assertThat(resultBuilder.substring(0, 1)).isEqualTo(lastYearNumber); - } + assertThat(result.substring(0, 1)).isEqualTo(lastYearNumber); + } - @Test - void shouldAddMonthValueSecond() { - var monthValue = "%02d".formatted(LocalDate.now().getMonthValue()); + @Test + void shouldAddMonthValueSecond() { + var monthHexValue = "%X".formatted(LocalDate.now().getMonthValue()); - var resultBuilder = vorgangNummerSupplier.initWithPrefix(); + var result = vorgangNummerSupplier.get(); - assertThat(resultBuilder.substring(1, 3)).isEqualTo(monthValue); - } + assertThat(result.substring(1, 2)).isEqualTo(monthHexValue); + } - @Test - void shouldAddDayValueThird() { - var dayValue = "%02d".formatted(LocalDate.now().getDayOfMonth()); + @Test + void shouldAddDayValueThird() { + var dayValue = "%02d".formatted(LocalDate.now().getDayOfMonth()); - var resultBuilder = vorgangNummerSupplier.initWithPrefix(); + var result = vorgangNummerSupplier.get(); - assertThat(resultBuilder.substring(3, 5)).isEqualTo(dayValue); - } + assertThat(result.substring(2, 4)).isEqualTo(dayValue); + } - @Test - void shouldAddHyphenAtEnd() { - var resultBuilder = vorgangNummerSupplier.initWithPrefix(); + @Test + void shouldAddHyphenAtEnd() { + var result = vorgangNummerSupplier.get(); - assertThat(resultBuilder.charAt(5)).isEqualTo('-'); - } + assertThat(result.charAt(4)).isEqualTo('-'); } } \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandler.java b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandler.java index c3977ab98b49d2abc6cc61d363549179ad6e6e7e..d98bdc6aaf6a18ee25015f577945b356205ae3a2 100644 --- a/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandler.java +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandler.java @@ -21,6 +21,8 @@ public class FormcycleExceptionHandler extends ResponseEntityExceptionHandler { static final String CREATE_VORGANG_EXCEPTION_MESSAGE = "Cannot create vorgang."; static final String UNEXPECTED_EXCEPTION_MESSAGE = "An unexpected error occurred."; + private static final String EXCEPTION_ID_TEMPLATE = "(ExceptionId:"; + @ExceptionHandler({ TechnicalException.class }) public ResponseEntity<InternalExceptionDto> handleTechnicalException(TechnicalException e, WebRequest request) { LOG.error(TECHNICAL_EXCEPTION_MESSAGE, e); @@ -29,12 +31,25 @@ public class FormcycleExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ StatusRuntimeException.class }) public ResponseEntity<InternalExceptionDto> handleStatusRuntimeException(StatusRuntimeException e, WebRequest request) { - LOG.error(CREATE_VORGANG_EXCEPTION_MESSAGE, e); - return buildResponseEntity(CREATE_VORGANG_EXCEPTION_MESSAGE, parseExceptionId(e.getMessage())); + var logMessage = TECHNICAL_EXCEPTION_MESSAGE; + var exceptionId = getExceptionId(e.getMessage()); + if (!hasExceptionId(e.getMessage())) { + logMessage = ExceptionUtil.formatMessageWithExceptionId(CREATE_VORGANG_EXCEPTION_MESSAGE, exceptionId); + } + LOG.error(logMessage, e); + return buildResponseEntity(CREATE_VORGANG_EXCEPTION_MESSAGE, exceptionId); + } + + boolean hasExceptionId(String message) { + return message.contains(EXCEPTION_ID_TEMPLATE); } - String parseExceptionId(String message) { - return message.substring(message.indexOf("(ExceptionId:") + 14, message.indexOf(")")); + String getExceptionId(String message) { + try { + return message.substring(message.indexOf(EXCEPTION_ID_TEMPLATE) + 14, message.indexOf(")")); + } catch (IndexOutOfBoundsException e) { + return createExceptionId(); + } } @ExceptionHandler({ RuntimeException.class }) diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandlerTest.java b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandlerTest.java index b4e9053c14a5e5a791262a76a0858ee919e3cfe2..f5e8ffaf282d688e800ab64cd87d57a478c0fd81 100644 --- a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandlerTest.java +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/FormcycleExceptionHandlerTest.java @@ -1,9 +1,11 @@ package de.ozgcloud.eingang.formcycle.common.errorhandling; -import static de.ozgcloud.eingang.formcycle.common.errorhandling.InternalExceptinDtoTestFactory.*; +import static de.ozgcloud.eingang.formcycle.common.errorhandling.InternalExceptionDtoTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -12,6 +14,7 @@ import org.mockito.Spy; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import de.ozgcloud.common.errorhandling.ExceptionUtil; import de.ozgcloud.common.errorhandling.TechnicalException; import io.grpc.StatusRuntimeException; @@ -46,35 +49,31 @@ class FormcycleExceptionHandlerTest { @Test void shouldCallBuildResponseEntity() { - doReturn(EXCEPTION_ID).when(exceptionHandler).parseExceptionId(any()); + when(statusRuntimeException.getStackTrace()).thenReturn(new StackTraceElement[0]); + when(statusRuntimeException.getMessage()).thenReturn(ExceptionUtil.formatMessageWithExceptionId(MESSAGE, EXCEPTION_ID)); exceptionHandler.handleStatusRuntimeException(statusRuntimeException, null); verify(exceptionHandler).buildResponseEntity(FormcycleExceptionHandler.CREATE_VORGANG_EXCEPTION_MESSAGE, EXCEPTION_ID); } - @Test - void shouldParseExceptionId() { - var parseExceptionId = exceptionHandler.parseExceptionId(InternalExceptinDtoTestFactory.messageWithExceptionId()); - - assertThat(parseExceptionId).isEqualTo(EXCEPTION_ID); - } - - } + @Nested + class TestGetExceptionId { - @Nested - class TestHandleUnexpectedException { + @Test + void shouldReturnExceptionIdFromMessage() { + var exceptionId = exceptionHandler.getExceptionId(messageWithExceptionId()); - private static final String EXCEPTION_MESSAGE = "Test"; + assertThat(exceptionId).isEqualTo(EXCEPTION_ID); + } - @Test - void shouldCallBuildResponseEntity() { - when(exceptionHandler.createExceptionId()).thenReturn(EXCEPTION_ID); + @Test + void shouldCreateNewExceptionId() { + var exceptionId = exceptionHandler.getExceptionId(MESSAGE); - exceptionHandler.handleUnexpectedException(new RuntimeException(EXCEPTION_MESSAGE), null); + assertThat(exceptionId).isNotEqualTo(EXCEPTION_ID); + } - verify(exceptionHandler).buildResponseEntity(FormcycleExceptionHandler.UNEXPECTED_EXCEPTION_MESSAGE, - EXCEPTION_ID); } } @@ -93,11 +92,11 @@ class FormcycleExceptionHandlerTest { void shouldCallBuildInternalExceptionDto() { buildResponseEntity(); - verify(exceptionHandler).buildInternalExceptionDto(InternalExceptinDtoTestFactory.MESSAGE, EXCEPTION_ID); + verify(exceptionHandler).buildInternalExceptionDto(InternalExceptionDtoTestFactory.MESSAGE, EXCEPTION_ID); } private ResponseEntity<InternalExceptionDto> buildResponseEntity() { - return exceptionHandler.buildResponseEntity(InternalExceptinDtoTestFactory.MESSAGE, EXCEPTION_ID); + return exceptionHandler.buildResponseEntity(InternalExceptionDtoTestFactory.MESSAGE, EXCEPTION_ID); } } @@ -116,11 +115,11 @@ class FormcycleExceptionHandlerTest { void shouldSetMessage() { var response = buildInternalExceptionDto(); - assertThat(response.getMessage()).isEqualTo(InternalExceptinDtoTestFactory.MESSAGE); + assertThat(response.getMessage()).isEqualTo(InternalExceptionDtoTestFactory.MESSAGE); } private InternalExceptionDto buildInternalExceptionDto() { - return exceptionHandler.buildInternalExceptionDto(InternalExceptinDtoTestFactory.MESSAGE, EXCEPTION_ID); + return exceptionHandler.buildInternalExceptionDto(InternalExceptionDtoTestFactory.MESSAGE, EXCEPTION_ID); } } diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptinDtoTestFactory.java b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptionDtoTestFactory.java similarity index 93% rename from formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptinDtoTestFactory.java rename to formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptionDtoTestFactory.java index 17121a3964962a7b65ff50f1ed5f1729f1f03640..98a8e5f27c53fd8676f1233ae24e3bf7c42306eb 100644 --- a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptinDtoTestFactory.java +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/ozgcloud/eingang/formcycle/common/errorhandling/InternalExceptionDtoTestFactory.java @@ -3,7 +3,7 @@ package de.ozgcloud.eingang.formcycle.common.errorhandling; import de.ozgcloud.common.errorhandling.ExceptionUtil; import de.ozgcloud.eingang.formcycle.common.errorhandling.InternalExceptionDto.InternalExceptionDtoBuilder; -public class InternalExceptinDtoTestFactory { +public class InternalExceptionDtoTestFactory { public static final String EXCEPTION_ID = "exception-id"; public static final String MESSAGE = "exception message";