feat(module): Implement callback subscription mechanism for module execution phases

This commit is contained in:
extio-de
2025-12-23 17:27:00 +01:00
parent ff9dcbe71b
commit a0b8fa49c1
7 changed files with 484 additions and 102 deletions

View File

@@ -24,9 +24,11 @@ public class ModuleExecutorImplTest {
}
@Test
public void testExecute_CallsAllPhasesForActiveAndDisplayedClientModule() {
public void testExecute_CallsAllPhasesForDisplayedClientModuleSubscribedToAllPhases() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
@@ -37,20 +39,24 @@ public class ModuleExecutorImplTest {
}
@Test
public void testExecute_CallsOnlyRunForActiveNonClientModule() {
public void testExecute_CallsOnlyRunForNonClientModule() {
final AbstractModule module = mock(AbstractModule.class);
when(this.moduleManager.getModulesActive()).thenReturn(List.of(module));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
verify(module).run();
verify(module, never()).onActivate(); // Just checking it doesn't call other things
verify(module, never()).onActivate();
}
@Test
public void testExecute_CallsOnlyRunForActiveButNotDisplayedClientModule() {
public void testExecute_SkipsUiPhaseForNotDisplayedClientModule() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of());
this.moduleExecutor.execute();
@@ -61,12 +67,14 @@ public class ModuleExecutorImplTest {
}
@Test
public void testExecute_HandlesExceptionInModule() {
public void testExecute_HandlesExceptionInRunPhase() {
final AbstractModule module1 = mock(AbstractModule.class);
final AbstractModule module2 = mock(AbstractModule.class);
doThrow(new RuntimeException("Test Exception")).when(module1).run();
when(this.moduleManager.getModulesActive()).thenReturn(List.of(module1, module2));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
@@ -79,7 +87,9 @@ public class ModuleExecutorImplTest {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
doThrow(new RuntimeException("UI Pre Exception")).when(clientModule).runUiPre();
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
@@ -90,36 +100,226 @@ public class ModuleExecutorImplTest {
}
@Test
public void testExecute_EmptyModules() {
when(this.moduleManager.getModulesActive()).thenReturn(List.of());
public void testExecute_NoSubscribersForCallback() {
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
verify(this.moduleManager, times(3)).getModulesActive();
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST);
}
@Test
public void testExecute_MultipleModules() {
public void testExecute_MultipleModulesWithMixedSubscriptions() {
final AbstractClientModule clientModule1 = mock(AbstractClientModule.class);
final AbstractClientModule clientModule2 = mock(AbstractClientModule.class);
final AbstractModule module1 = mock(AbstractModule.class);
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule1, clientModule2, module1));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule1, clientModule2));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule1, clientModule2, module1));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule1));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule1));
this.moduleExecutor.execute();
// clientModule1: displayed, so all phases
verify(clientModule1).runUiPre();
verify(clientModule1).run();
verify(clientModule1).runUiPost();
// clientModule2: active but not displayed, so only run
verify(clientModule2, never()).runUiPre();
verify(clientModule2).run();
verify(clientModule2, never()).runUiPost();
// module1: only run
verify(module1).run();
}
@Test
public void testExecute_ModuleSubscribedOnlyToUiPre() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(clientModule, never()).run();
verify(clientModule, never()).runUiPost();
}
@Test
public void testExecute_ModuleSubscribedOnlyToRun() {
final AbstractModule module = mock(AbstractModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
verify(module).run();
}
@Test
public void testExecute_ClientModuleSubscribedOnlyToUiPost() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule, never()).runUiPre();
verify(clientModule, never()).run();
verify(clientModule).runUiPost();
}
@Test
public void testExecute_ClientModuleSubscribedToUiPreAndRun_NotUiPost() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(clientModule).run();
verify(clientModule, never()).runUiPost();
}
@Test
public void testExecute_ClientModuleSubscribedToUiPreAndUiPost_NotRun() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(clientModule, never()).run();
verify(clientModule).runUiPost();
}
@Test
public void testExecute_ClientModuleSubscribedToRunAndUiPost_NotUiPre() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule, never()).runUiPre();
verify(clientModule).run();
verify(clientModule).runUiPost();
}
@Test
public void testExecute_HandleExceptionInUiPost() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
doThrow(new RuntimeException("UI Post Exception")).when(clientModule).runUiPost();
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(clientModule).run();
verify(clientModule).runUiPost();
}
@Test
public void testExecute_HandleExceptionInRun() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
doThrow(new RuntimeException("Run Exception")).when(clientModule).run();
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(clientModule).run();
verify(clientModule).runUiPost();
}
@Test
public void testExecute_MultipleExceptionsInDifferentPhases() {
final AbstractModule module1 = mock(AbstractModule.class);
final AbstractModule module2 = mock(AbstractModule.class);
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
doThrow(new RuntimeException("Exception in module1")).when(module1).run();
doThrow(new RuntimeException("Exception in clientModule")).when(clientModule).runUiPre();
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
this.moduleExecutor.execute();
verify(clientModule).runUiPre();
verify(module1).run();
verify(module2).run();
}
@Test
public void testExecute_ClientModuleDisplayedButNotSubscribedToUiPhases() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
verify(clientModule, never()).runUiPre();
verify(clientModule).run();
verify(clientModule, never()).runUiPost();
}
@Test
public void testExecute_ClientModuleNotDisplayedButSubscribedToRunOnly() {
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
this.moduleExecutor.execute();
verify(clientModule, never()).runUiPre();
verify(clientModule).run();
verify(clientModule, never()).runUiPost();
}
@Test
public void testExecute_MultipleNonClientModulesSubscribedToDifferentCallbacks() {
final AbstractModule module1 = mock(AbstractModule.class);
final AbstractModule module2 = mock(AbstractModule.class);
final AbstractModule module3 = mock(AbstractModule.class);
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(module1));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2, module3));
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(module3));
this.moduleExecutor.execute();
verify(module1).run();
verify(module2).run();
verify(module3).run();
}
}

View File

@@ -408,6 +408,95 @@ public class ModuleManagerTest {
assertFalse(this.moduleManager.isModal());
}
@Test
public void testGetSubscribersForCallback_NoSubscribers() {
final List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertTrue(subscribers.isEmpty());
}
@Test
public void testGetSubscribersForCallback_WithSubscriber() {
this.moduleManager.loadModule(CallbackSubscriberModule.class);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
final List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertEquals(1, subscribers.size());
assertTrue(subscribers.stream().anyMatch(m -> m instanceof CallbackSubscriberModule));
}
@Test
public void testGetSubscribersForCallback_MultipleCallbacks() {
this.moduleManager.loadModule(MultiCallbackSubscriberModule.class);
this.moduleManager.changeActiveState(MultiCallbackSubscriberModule.class, true);
final List<AbstractModule> uiPreSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
final List<AbstractModule> runSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
final List<AbstractModule> uiPostSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST);
assertEquals(1, uiPreSubscribers.size());
assertEquals(1, runSubscribers.size());
assertEquals(1, uiPostSubscribers.size());
assertTrue(uiPreSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
assertTrue(runSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
assertTrue(uiPostSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
}
@Test
public void testGetSubscribersForCallback_UnsubscribedCallbackEmpty() {
this.moduleManager.loadModule(CallbackSubscriberModule.class);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
final List<AbstractModule> runSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
assertTrue(runSubscribers.isEmpty());
}
@Test
public void testGetSubscribersForCallback_RemovedOnDeactivate() {
this.moduleManager.loadModule(CallbackSubscriberModule.class);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertEquals(1, subscribers.size());
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, false);
subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertTrue(subscribers.isEmpty());
}
@Test
public void testGetSubscribersForCallback_MultipleModules() {
this.moduleManager.loadModule(CallbackSubscriberModule.class);
this.moduleManager.loadModule(MultiCallbackSubscriberModule.class);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
this.moduleManager.changeActiveState(MultiCallbackSubscriberModule.class, true);
final List<AbstractModule> uiPreSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertEquals(2, uiPreSubscribers.size());
assertTrue(uiPreSubscribers.stream().anyMatch(m -> m instanceof CallbackSubscriberModule));
assertTrue(uiPreSubscribers.stream().anyMatch(m -> m instanceof MultiCallbackSubscriberModule));
}
@Test
public void testGetSubscribersForCallback_ReactivateModule() {
this.moduleManager.loadModule(CallbackSubscriberModule.class);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, false);
List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertTrue(subscribers.isEmpty());
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
assertEquals(1, subscribers.size());
}
static class TestModule extends AbstractModule {
boolean onLoadCalled = false;
@@ -504,4 +593,26 @@ public class ModuleManagerTest {
}
static class CallbackSubscriberModule extends AbstractModule {
@Override
public java.util.List<ModuleExecutorCallbacks> executorCallbackSubscriptions() {
return java.util.List.of(ModuleExecutorCallbacks.UI_PRE);
}
}
static class MultiCallbackSubscriberModule extends AbstractModule {
@Override
public java.util.List<ModuleExecutorCallbacks> executorCallbackSubscriptions() {
return java.util.List.of(
ModuleExecutorCallbacks.UI_PRE,
ModuleExecutorCallbacks.RUN,
ModuleExecutorCallbacks.UI_POST
);
}
}
}