@@ -382,7 +382,33 @@ public void testStopWithoutStartIsNoOp() {
382382 verifyNoInteractions (mockMetrics );
383383 }
384384
385- // ---- DLQ topic validation tests (no thread start required) ----
385+ @ Test
386+ public void testDlqBeforeStartFailsWithIllegalState () {
387+ stateManager = builder ().build ();
388+ // dlq() is invoked without a prior start(); the lifecycle guard must reject it with a
389+ // failed future rather than enqueueing onto a sender thread that is not running.
390+ CompletableFuture <Void > result = stateManager .dlq (param ());
391+ assertTrue (result .isDone ());
392+ assertTrue (result .isCompletedExceptionally ());
393+ assertInstanceOf (IllegalStateException .class , getCause (result ));
394+ verifyNoInteractions (mockMetrics );
395+ }
396+
397+ @ Test
398+ public void testDlqAfterStopFailsWithIllegalState () throws Exception {
399+ stateManager = builder ().build ();
400+ stateManager .start ();
401+ stateManager .stop ();
402+ // Once stopped, dlq() must fail fast rather than enqueueing onto a shut-down sender thread
403+ // where the future would never complete.
404+ CompletableFuture <Void > result = stateManager .dlq (param ());
405+ assertTrue (result .isDone ());
406+ assertTrue (result .isCompletedExceptionally ());
407+ assertInstanceOf (IllegalStateException .class , getCause (result ));
408+ verifyNoInteractions (mockMetrics );
409+ }
410+
411+ // ---- DLQ topic validation tests ----
386412
387413 @ Test
388414 public void testDlqEmptyTopicNameFailsValidation () throws Exception {
@@ -391,6 +417,7 @@ public void testDlqEmptyTopicNameFailsValidation() throws Exception {
391417 when (cacheHelper .shareGroupDlqTopicPrefix ()).thenReturn (Optional .empty ());
392418
393419 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
420+ stateManager .start ();
394421 Throwable cause = getCause (stateManager .dlq (param ()));
395422 assertInstanceOf (ConfigException .class , cause );
396423 assertTrue (cause .getMessage ().contains ("empty" ));
@@ -404,6 +431,7 @@ public void testDlqTopicStartingWithUnderscoreFailsValidation() throws Exception
404431 when (cacheHelper .shareGroupDlqTopicPrefix ()).thenReturn (Optional .empty ());
405432
406433 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
434+ stateManager .start ();
407435 Throwable cause = getCause (stateManager .dlq (param ()));
408436 assertInstanceOf (ConfigException .class , cause );
409437 assertTrue (cause .getMessage ().contains ("__" ));
@@ -419,6 +447,7 @@ public void testDlqExistingTopicWithoutDlqConfigFailsValidation() throws Excepti
419447 when (cacheHelper .isDlqEnabledOnTopic (DLQ_TOPIC )).thenReturn (false );
420448
421449 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
450+ stateManager .start ();
422451 Throwable cause = getCause (stateManager .dlq (param ()));
423452 assertInstanceOf (ConfigException .class , cause );
424453 assertTrue (cause .getMessage ().contains ("DLQ is not enabled" ));
@@ -434,6 +463,7 @@ public void testDlqTopicMissingAndAutoCreateDisabledFailsValidation() throws Exc
434463 when (cacheHelper .isDlqAutoTopicCreateEnabled ()).thenReturn (false );
435464
436465 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
466+ stateManager .start ();
437467 Throwable cause = getCause (stateManager .dlq (param ()));
438468 assertInstanceOf (ConfigException .class , cause );
439469 assertTrue (cause .getMessage ().contains ("auto create is disabled" ));
@@ -449,24 +479,28 @@ public void testDlqTopicPrefixMismatchFailsValidation() throws Exception {
449479 when (cacheHelper .isDlqEnabledOnTopic (DLQ_TOPIC )).thenReturn (true );
450480
451481 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
482+ stateManager .start ();
452483 Throwable cause = getCause (stateManager .dlq (param ()));
453484 assertInstanceOf (ConfigException .class , cause );
454485 assertTrue (cause .getMessage ().contains ("does not comply with the DLQ topic prefix" ));
455486 verifyNoInteractions (mockMetrics );
456487 }
457488
458489 @ Test
459- public void testDlqValidationFailureCompletesFutureBeforeStart () throws Exception {
490+ public void testDlqValidationFailureCompletesFutureSynchronously () throws Exception {
460491 ShareGroupDLQMetadataCacheHelper cacheHelper = mock (ShareGroupDLQMetadataCacheHelper .class );
461492 when (cacheHelper .shareGroupDlqTopic (GROUP_ID )).thenReturn (Optional .empty ());
462493 when (cacheHelper .shareGroupDlqTopicPrefix ()).thenReturn (Optional .empty ());
463494
464- // validateDlqTopic runs synchronously inside dlq(), so it should fail without the sender thread.
495+ // validateDlqTopic runs synchronously inside dlq() on the calling thread, so a validation
496+ // failure completes the returned future before dlq() returns - no sender-thread round trip.
465497 stateManager = builder ().withCacheHelper (cacheHelper ).build ();
498+ stateManager .start ();
466499 CompletableFuture <Void > result = stateManager .dlq (param ());
467500 assertTrue (result .isDone ());
468501 assertTrue (result .isCompletedExceptionally ());
469502 assertFalse (result .isCancelled ());
503+ assertInstanceOf (ConfigException .class , getCause (result ));
470504 verifyNoInteractions (mockMetrics );
471505 }
472506
0 commit comments