diff --git a/src/test/java/com/diamonddagger590/mccore/configuration/task/ReloadableTaskTest.java b/src/test/java/com/diamonddagger590/mccore/configuration/task/ReloadableTaskTest.java index 8331ab2..ffcc168 100644 --- a/src/test/java/com/diamonddagger590/mccore/configuration/task/ReloadableTaskTest.java +++ b/src/test/java/com/diamonddagger590/mccore/configuration/task/ReloadableTaskTest.java @@ -121,4 +121,28 @@ void constructor_doesNotCancelInitialTask() { verify(initialTask, never()).cancelTask(); } + + @Test + @DisplayName("Given a ReloadableTask, when first constructed, then runTask is called on the initial task") + void constructor_callsRunTaskOnInitialTask() { + CancelableCoreTask initialTask = mock(CancelableCoreTask.class); + BiFunction callback = (doc, r) -> initialTask; + + new ReloadableTask<>(yamlDocument, route, callback, false); + + verify(initialTask).runTask(false); + } + + @Test + @DisplayName("Given async=true, when first constructed, then initial runTask receives false due to field initialization order") + void constructor_callsRunTaskWithFalse_whenAsyncIsTrueDueToFieldInitOrder() { + CancelableCoreTask initialTask = mock(CancelableCoreTask.class); + BiFunction callback = (doc, r) -> initialTask; + + new ReloadableTask<>(yamlDocument, route, callback, true); + + // During construction, super() calls reloadContent() before 'async' is assigned. + // The async field is still at its default value (false) at that point. + verify(initialTask).runTask(false); + } } diff --git a/src/test/java/com/diamonddagger590/mccore/database/transaction/FailSafeTransactionTest.java b/src/test/java/com/diamonddagger590/mccore/database/transaction/FailSafeTransactionTest.java index e89d53a..83a43fb 100644 --- a/src/test/java/com/diamonddagger590/mccore/database/transaction/FailSafeTransactionTest.java +++ b/src/test/java/com/diamonddagger590/mccore/database/transaction/FailSafeTransactionTest.java @@ -128,6 +128,50 @@ void executeTransaction_doesNotThrow_whenAutoCommitResetAndCloseBothFail() throw assertDoesNotThrow(transaction::executeTransaction); } + @Test + @DisplayName("Given a successful execution, when commit throws, then transaction state is ROLLED_BACK and rollback is called") + void executeTransaction_rollsBack_whenCommitFails() throws SQLException { + PreparedStatement ps = mock(PreparedStatement.class); + doThrow(new SQLException("commit failed")).when(connection).commit(); + + var transaction = new FailSafeTransaction(connection, List.of(ps)); + assertDoesNotThrow(transaction::executeTransaction); + + verify(ps).executeUpdate(); + verify(connection).rollback(); + assertEquals(TransactionState.ROLLED_BACK, transaction.getTransactionState()); + assertTrue(transaction.getFailureCause().isPresent()); + } + + @Test + @DisplayName("Given a statement failure, when getting failure cause, then cause message wraps the original exception") + void executeTransaction_storesFailureCause_withOriginalExceptionDetails() throws SQLException { + PreparedStatement ps = mock(PreparedStatement.class); + doThrow(new SQLException("specific error detail")).when(ps).executeUpdate(); + + var transaction = new FailSafeTransaction(connection, List.of(ps)); + transaction.executeTransaction(); + + assertTrue(transaction.getFailureCause().isPresent()); + assertNotNull(transaction.getFailureCause().get().getCause()); + assertEquals("specific error detail", transaction.getFailureCause().get().getCause().getMessage()); + } + + @Test + @DisplayName("Given a successful execution with autocommit-reset failure, when checking state, then state remains COMMITTED") + void executeTransaction_remainsCommitted_whenAutoCommitResetFails() throws SQLException { + PreparedStatement ps = mock(PreparedStatement.class); + doNothing().when(connection).setAutoCommit(false); + doThrow(new SQLException("autocommit fail")).when(connection).setAutoCommit(true); + + var transaction = new FailSafeTransaction(connection, List.of(ps)); + transaction.executeTransaction(); + + verify(connection).commit(); + assertEquals(TransactionState.COMMITTED, transaction.getTransactionState()); + assertFalse(transaction.getFailureCause().isPresent()); + } + @Test @DisplayName("Given no statements, when executing transaction, then commit succeeds and no rollback occurs") void executeTransaction_commitsSuccessfully_whenNoStatementsAdded() throws SQLException { diff --git a/src/test/java/com/diamonddagger590/mccore/exception/localization/NoLocalizationContainsMessageExceptionTest.java b/src/test/java/com/diamonddagger590/mccore/exception/localization/NoLocalizationContainsMessageExceptionTest.java index 62ad367..bd94075 100644 --- a/src/test/java/com/diamonddagger590/mccore/exception/localization/NoLocalizationContainsMessageExceptionTest.java +++ b/src/test/java/com/diamonddagger590/mccore/exception/localization/NoLocalizationContainsMessageExceptionTest.java @@ -63,6 +63,30 @@ void getCheckedLocales_returnsEmptySet_whenConstructedWithEmptyLocales() { assertTrue(ex.getCheckedLocales().isEmpty()); } + @Test + @DisplayName("Given an empty locale set, when calling getMessage, then message contains Optional.empty for locales") + void getMessage_containsOptionalEmpty_whenConstructedWithEmptyLocales() { + Route route = Route.from("messages", "empty"); + NoLocalizationContainsMessageException ex = new NoLocalizationContainsMessageException(route, Set.of()); + + String message = ex.getMessage(); + assertNotNull(message); + assertTrue(message.contains(route.toString())); + assertTrue(message.contains("Optional.empty")); + } + + @Test + @DisplayName("Given multiple locales, when calling getMessage, then message contains comma-separated locale names") + void getMessage_containsCommaSeparatedLocaleNames_whenConstructedWithMultipleLocales() { + Route route = Route.from("messages", "multi"); + NoLocalizationContainsMessageException ex = new NoLocalizationContainsMessageException(route, Set.of(Locale.ENGLISH, Locale.FRENCH)); + + String message = ex.getMessage(); + assertNotNull(message); + assertTrue(message.contains(Locale.ENGLISH.getDisplayName())); + assertTrue(message.contains(Locale.FRENCH.getDisplayName())); + } + @Test @DisplayName("Given a NoLocalizationContainsMessageException, when checking type, then it is a RuntimeException") void noLocalizationContainsMessageException_isRuntimeException_always() {