From 548bf97c57dbc345b88f078518a880123c1f9e3c Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 1 Apr 2020 16:01:26 +0200 Subject: [PATCH] make scm-webapp depend optional of scm-landingpage-plugin // add some events for landingpage --- scm-webapp/pom.xml | 9 ++ .../scm/plugin/DefaultPluginManager.java | 7 +- .../sonia/scm/plugin/PluginCenterLoader.java | 15 ++- .../java/sonia/scm/plugin/PluginEvent.java | 39 +++++++ .../sonia/scm/plugin/PluginEventType.java | 29 +++++ ...uginInstallationFailedEventSubscriber.java | 86 ++++++++++++++ .../PluginInstalledEventSubscriber.java | 98 ++++++++++++++++ .../RepositoryCreatedEventSubscriber.java | 88 ++++++++++++++ .../scm/plugin/DefaultPluginManagerTest.java | 38 ++++++ .../scm/plugin/PluginCenterLoaderTest.java | 20 ++++ ...InstallationFailedEventSubscriberTest.java | 98 ++++++++++++++++ .../PluginInstalledEventSubscriberTest.java | 106 +++++++++++++++++ .../RepositoryCreatedEventSubscriberTest.java | 109 ++++++++++++++++++ yarn.lock | 11 +- 14 files changed, 741 insertions(+), 12 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PluginEvent.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PluginEventType.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriber.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PluginInstalledEventSubscriber.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/RepositoryCreatedEventSubscriber.java create mode 100644 scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriberTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/plugin/PluginInstalledEventSubscriberTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/RepositoryCreatedEventSubscriberTest.java diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index f7ae938b5b..1d85d6a2df 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -463,6 +463,15 @@ provided + + + + sonia.scm.plugins + scm-landingpage-plugin + 1.0.0-SNAPSHOT + true + + diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index ba41ada287..40b6230261 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -31,7 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.NotFoundException; import sonia.scm.event.ScmEventBus; -import sonia.scm.lifecycle.RestartEvent; import sonia.scm.lifecycle.Restarter; import sonia.scm.version.Version; @@ -64,17 +63,19 @@ public class DefaultPluginManager implements PluginManager { private final PluginCenter center; private final PluginInstaller installer; private final Restarter restarter; + private final ScmEventBus eventBus; private final Collection pendingInstallQueue = new ArrayList<>(); private final Collection pendingUninstallQueue = new ArrayList<>(); private final PluginDependencyTracker dependencyTracker = new PluginDependencyTracker(); @Inject - public DefaultPluginManager(PluginLoader loader, PluginCenter center, PluginInstaller installer, Restarter restarter) { + public DefaultPluginManager(PluginLoader loader, PluginCenter center, PluginInstaller installer, Restarter restarter, ScmEventBus eventBus) { this.loader = loader; this.center = center; this.installer = installer; this.restarter = restarter; + this.eventBus = eventBus; this.computeInstallationDependencies(); } @@ -172,8 +173,10 @@ public class DefaultPluginManager implements PluginManager { PendingPluginInstallation pending = installer.install(plugin); dependencyTracker.addInstalled(plugin.getDescriptor()); pendingInstallations.add(pending); + eventBus.post(new PluginEvent(PluginEventType.INSTALLED, plugin)); } catch (PluginInstallException ex) { cancelPending(pendingInstallations); + eventBus.post(new PluginEvent(PluginEventType.INSTALLATION_FAILED, plugin)); throw ex; } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java index efc7d9f136..16f3123157 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java @@ -27,10 +27,11 @@ package sonia.scm.plugin; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.event.Event; +import sonia.scm.event.ScmEventBus; import sonia.scm.net.ahc.AdvancedHttpClient; import javax.inject.Inject; -import java.io.IOException; import java.util.Collections; import java.util.Set; @@ -40,16 +41,18 @@ class PluginCenterLoader { private final AdvancedHttpClient client; private final PluginCenterDtoMapper mapper; + private final ScmEventBus eventBus; @Inject - public PluginCenterLoader(AdvancedHttpClient client) { - this(client, PluginCenterDtoMapper.INSTANCE); + public PluginCenterLoader(AdvancedHttpClient client, ScmEventBus eventBus) { + this(client, PluginCenterDtoMapper.INSTANCE, eventBus); } @VisibleForTesting - PluginCenterLoader(AdvancedHttpClient client, PluginCenterDtoMapper mapper) { + PluginCenterLoader(AdvancedHttpClient client, PluginCenterDtoMapper mapper, ScmEventBus eventBus) { this.client = client; this.mapper = mapper; + this.eventBus = eventBus; } Set load(String url) { @@ -59,8 +62,12 @@ class PluginCenterLoader { return mapper.map(pluginCenterDto); } catch (Exception ex) { LOG.error("failed to load plugins from plugin center, returning empty list", ex); + eventBus.post(new PluginCenterNotAvailableEvent()); return Collections.emptySet(); } } + @Event + class PluginCenterNotAvailableEvent {} + } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginEvent.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginEvent.java new file mode 100644 index 0000000000..45716ae04b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginEvent.java @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import lombok.Getter; +import sonia.scm.event.Event; + +@Getter +@Event +public class PluginEvent { + private final PluginEventType eventType; + private final AvailablePlugin plugin; + public PluginEvent(PluginEventType eventType, AvailablePlugin plugin) { + this.eventType = eventType; + this.plugin = plugin; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginEventType.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginEventType.java new file mode 100644 index 0000000000..2cde7c35f8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginEventType.java @@ -0,0 +1,29 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +enum PluginEventType { + INSTALLED, INSTALLATION_FAILED +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriber.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriber.java new file mode 100644 index 0000000000..5fd4188606 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriber.java @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import com.cloudogu.scm.myevents.MyEvent; +import com.github.legman.Subscribe; +import lombok.Getter; +import lombok.NoArgsConstructor; +import sonia.scm.EagerSingleton; +import sonia.scm.event.ScmEventBus; +import sonia.scm.xml.XmlInstantAdapter; + +import javax.inject.Inject; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; + +@Extension(requires = "scm-landingpage-plugin") +@EagerSingleton +public class PluginInstallationFailedEventSubscriber { + + private final ScmEventBus eventBus; + + @Inject + public PluginInstallationFailedEventSubscriber(ScmEventBus eventBus) { + this.eventBus = eventBus; + } + + @Subscribe + public void handleEvent(PluginEvent pluginEvent) { + if (pluginEvent.getEventType() == PluginEventType.INSTALLATION_FAILED) { + AvailablePlugin newPlugin = pluginEvent.getPlugin(); + + String permission = PluginPermissions.manage().asShiroString(); + String pluginName = newPlugin.getDescriptor().getInformation().getDisplayName(); + + String pluginVersion = newPlugin.getDescriptor().getInformation().getVersion(); + + eventBus.post(new PluginInstallationFailedEvent(permission, pluginName, pluginVersion, Instant.now())); + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + @Getter + @NoArgsConstructor + static class PluginInstallationFailedEvent extends MyEvent { + private String pluginName; + private String pluginVersion; + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant date; + + PluginInstallationFailedEvent(String permission, String pluginName, String pluginVersion, Instant date) { + super(PluginInstalledEventSubscriber.PluginInstalledEvent.class.getSimpleName(), permission); + + this.pluginName = pluginName; + this.pluginVersion = pluginVersion; + this.date = date; + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstalledEventSubscriber.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstalledEventSubscriber.java new file mode 100644 index 0000000000..4913eb80cf --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstalledEventSubscriber.java @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import com.cloudogu.scm.myevents.MyEvent; +import com.github.legman.Subscribe; +import lombok.Getter; +import lombok.NoArgsConstructor; +import sonia.scm.EagerSingleton; +import sonia.scm.event.ScmEventBus; +import sonia.scm.xml.XmlInstantAdapter; + +import javax.inject.Inject; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; +import java.util.Optional; + +@Extension(requires = "scm-landingpage-plugin") +@EagerSingleton +public class PluginInstalledEventSubscriber { + + private final ScmEventBus eventBus; + private final PluginManager pluginManager; + + @Inject + public PluginInstalledEventSubscriber(ScmEventBus eventBus, PluginManager pluginManager) { + this.eventBus = eventBus; + this.pluginManager = pluginManager; + } + + @Subscribe + public void handleEvent(PluginEvent pluginEvent) { + if (pluginEvent.getEventType() == PluginEventType.INSTALLED) { + AvailablePlugin newPlugin = pluginEvent.getPlugin(); + Optional installedPlugin = pluginManager.getInstalled(newPlugin.getDescriptor().getInformation().getDisplayName()); + + String permission = PluginPermissions.manage().asShiroString(); + + String pluginName = newPlugin.getDescriptor().getInformation().getName(); + + String previousPluginVersion = null; + if (installedPlugin.isPresent()) { + previousPluginVersion = installedPlugin.get().getDescriptor().getInformation().getVersion(); + } + + String newPluginVersion = newPlugin.getDescriptor().getInformation().getVersion(); + + eventBus.post(new PluginInstalledEvent(permission, pluginName, previousPluginVersion, newPluginVersion, Instant.now())); + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + @Getter + @NoArgsConstructor + static class PluginInstalledEvent extends MyEvent { + private String pluginName; + private String previousPluginVersion; + private String newPluginVersion; + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant date; + + PluginInstalledEvent(String permission, String pluginName, String previousPluginVersion, String newPluginVersion, Instant date) { + super(PluginInstalledEventSubscriber.PluginInstalledEvent.class.getSimpleName(), permission); + + this.pluginName = pluginName; + this.previousPluginVersion = previousPluginVersion; + this.newPluginVersion = newPluginVersion; + this.date = date; + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryCreatedEventSubscriber.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryCreatedEventSubscriber.java new file mode 100644 index 0000000000..746d73b2d2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryCreatedEventSubscriber.java @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import com.cloudogu.scm.myevents.MyEvent; +import com.github.legman.Subscribe; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.apache.shiro.SecurityUtils; +import sonia.scm.EagerSingleton; +import sonia.scm.HandlerEventType; +import sonia.scm.event.ScmEventBus; +import sonia.scm.plugin.Extension; +import sonia.scm.user.User; +import sonia.scm.xml.XmlInstantAdapter; + +import javax.inject.Inject; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; + +@Extension(requires = "scm-landingpage-plugin") +@EagerSingleton +public class RepositoryCreatedEventSubscriber { + + private final ScmEventBus eventBus; + + @Inject + public RepositoryCreatedEventSubscriber(ScmEventBus eventBus) { + this.eventBus = eventBus; + } + + @Subscribe + public void handleEvent(RepositoryEvent event) { + if (event.getEventType() == HandlerEventType.CREATE) { + Repository eventRepo = event.getItem(); + String permission = RepositoryPermissions.read(event.getItem()).asShiroString(); + String repository = eventRepo.getNamespace() + "/" + eventRepo.getName(); + String creator = SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getDisplayName(); + Instant date = Instant.now(); + + eventBus.post(new RepositoryCreatedEvent(permission, repository, creator, date)); + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + @Getter + @NoArgsConstructor + static class RepositoryCreatedEvent extends MyEvent { + private String repository; + private String creator; + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant date; + + RepositoryCreatedEvent(String permission, String repository, String creator, Instant date) { + super(RepositoryCreatedEvent.class.getSimpleName(), permission); + this.repository = repository; + this.creator = creator; + this.date = date; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index 8f3f7bbe57..aa4d5cb5b1 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -36,16 +36,19 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junitpioneer.jupiter.TempDirectory; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.NotFoundException; import sonia.scm.ScmConstraintViolationException; +import sonia.scm.event.ScmEventBus; import sonia.scm.lifecycle.Restarter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -53,6 +56,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; @@ -83,6 +87,12 @@ class DefaultPluginManagerTest { @Mock private Restarter restarter; + @Mock + private ScmEventBus eventBus; + + @Captor + private ArgumentCaptor eventCaptor; + @InjectMocks private DefaultPluginManager manager; @@ -537,8 +547,36 @@ class DefaultPluginManagerTest { verify(installer, never()).install(oldScriptPlugin); } + + @Test + void shouldFirePluginEventOnInstallation() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review)); + + manager.install("scm-review-plugin", false); + + verify(eventBus).post(eventCaptor.capture()); + + assertThat(eventCaptor.getValue().getEventType()).isEqualTo(PluginEventType.INSTALLED); + assertThat(eventCaptor.getValue().getPlugin()).isEqualTo(review); + } + + @Test + void shouldFirePluginEventOnFailedInstallation() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review)); + doThrow(new PluginDownloadException(review, new IOException())).when(installer).install(review); + + assertThrows(PluginDownloadException.class, () -> manager.install("scm-review-plugin", false)); + + verify(eventBus).post(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getEventType()).isEqualTo(PluginEventType.INSTALLATION_FAILED); + assertThat(eventCaptor.getValue().getPlugin()).isEqualTo(review); + } + } + @Nested class WithoutReadPermissions { diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java index 01fe540206..b00241eebf 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java @@ -24,12 +24,16 @@ package sonia.scm.plugin; +import com.sun.mail.iap.Argument; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.event.ScmEventBus; import sonia.scm.net.ahc.AdvancedHttpClient; import java.io.IOException; @@ -37,6 +41,10 @@ import java.util.Collections; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -50,6 +58,9 @@ class PluginCenterLoaderTest { @Mock private PluginCenterDtoMapper mapper; + @Mock + private ScmEventBus eventBus; + @InjectMocks private PluginCenterLoader loader; @@ -71,4 +82,13 @@ class PluginCenterLoaderTest { Set fetch = loader.load(PLUGIN_URL); assertThat(fetch).isEmpty(); } + + @Test + void shouldThrowExceptionAndFirePluginCenterNotAvailableEvent() throws IOException { + when(client.get(PLUGIN_URL).request()).thenThrow(new IOException("failed to fetch")); + + loader.load(PLUGIN_URL); + + verify(eventBus).post(any(PluginCenterLoader.PluginCenterNotAvailableEvent.class)); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriberTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriberTest.java new file mode 100644 index 0000000000..88dff091cf --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallationFailedEventSubscriberTest.java @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.event.ScmEventBus; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PluginInstallationFailedEventSubscriberTest { + + private AvailablePlugin newPlugin = PluginTestHelper.createAvailable("scm-hitchhiker-plugin"); + + @Mock + private Subject subject; + + @Mock + private ScmEventBus eventBus; + + @Mock + private PluginEvent event; + + @Captor + private ArgumentCaptor eventCaptor; + + @InjectMocks + private PluginInstallationFailedEventSubscriber subscriber; + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void tearDownSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldFireMyEvent() { + when(event.getEventType()).thenReturn(PluginEventType.INSTALLATION_FAILED); + when(event.getPlugin()).thenReturn(newPlugin); + + subscriber.handleEvent(event); + + verify(eventBus).post(eventCaptor.capture()); + + PluginInstallationFailedEventSubscriber.PluginInstallationFailedEvent pluginInstalledEvent = eventCaptor.getValue(); + assertThat(pluginInstalledEvent.getPermission()).isEqualTo("plugin:manage"); + assertThat(pluginInstalledEvent.getPluginVersion()).isEqualTo("1.0"); + assertThat(pluginInstalledEvent.getPluginName()).isEqualTo(newPlugin.getDescriptor().getInformation().getDisplayName()); + } + + @Test + void shouldNotFireMyEventWhenNotCreatedEvent() { + when(event.getEventType()).thenReturn(PluginEventType.INSTALLED); + subscriber.handleEvent(event); + + verify(eventBus, never()).post(eventCaptor.capture()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstalledEventSubscriberTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstalledEventSubscriberTest.java new file mode 100644 index 0000000000..61348c96c3 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstalledEventSubscriberTest.java @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.event.ScmEventBus; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PluginInstalledEventSubscriberTest { + + private InstalledPlugin oldPlugin = PluginTestHelper.createInstalled("scm-hitchhiker-plugin"); + private AvailablePlugin newPlugin = PluginTestHelper.createAvailable("scm-hitchhiker-plugin", "1.1"); + + @Mock + private Subject subject; + + @Mock + private ScmEventBus eventBus; + + @Mock + private PluginEvent event; + + @Mock + private PluginManager pluginManager; + + @Captor + private ArgumentCaptor eventCaptor; + + @InjectMocks + private PluginInstalledEventSubscriber subscriber; + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void tearDownSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldFireMyEvent() { + when(event.getEventType()).thenReturn(PluginEventType.INSTALLED); + when(event.getPlugin()).thenReturn(newPlugin); + when(pluginManager.getInstalled("scm-hitchhiker-plugin")).thenReturn(Optional.of(oldPlugin)); + + subscriber.handleEvent(event); + + verify(eventBus).post(eventCaptor.capture()); + + PluginInstalledEventSubscriber.PluginInstalledEvent pluginInstalledEvent = eventCaptor.getValue(); + assertThat(pluginInstalledEvent.getPermission()).isEqualTo("plugin:manage"); + assertThat(pluginInstalledEvent.getPreviousPluginVersion()).isEqualTo("1.0"); + assertThat(pluginInstalledEvent.getNewPluginVersion()).isEqualTo("1.1"); + assertThat(pluginInstalledEvent.getPluginName()).isEqualTo(newPlugin.getDescriptor().getInformation().getDisplayName()); + } + + @Test + void shouldNotFireMyEventWhenNotCreatedEvent() { + when(event.getEventType()).thenReturn(PluginEventType.INSTALLATION_FAILED); + subscriber.handleEvent(event); + + verify(eventBus, never()).post(eventCaptor.capture()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryCreatedEventSubscriberTest.java b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryCreatedEventSubscriberTest.java new file mode 100644 index 0000000000..59930d1709 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryCreatedEventSubscriberTest.java @@ -0,0 +1,109 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.HandlerEventType; +import sonia.scm.event.ScmEventBus; +import sonia.scm.user.User; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RepositoryCreatedEventSubscriberTest { + + private Repository REPOSITORY = new Repository("1", "git", "nicest", "repo"); + + @Mock + private Subject subject; + + @Mock + private ScmEventBus eventBus; + + @Mock + private RepositoryEvent event; + + @Mock + private PrincipalCollection principalCollection; + + @Captor + private ArgumentCaptor eventCaptor; + + @InjectMocks + private RepositoryCreatedEventSubscriber subscriber; + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void tearDownSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldFireMyEvent() { + User trillian = new User("trillian", "Trillian", "tricia@hitchhiker.org"); + + when(event.getEventType()).thenReturn(HandlerEventType.CREATE); + when(event.getItem()).thenReturn(REPOSITORY); + when(subject.getPrincipals()).thenReturn(principalCollection); + when(principalCollection.oneByType(User.class)).thenReturn(trillian); + + subscriber.handleEvent(event); + + verify(eventBus).post(eventCaptor.capture()); + + RepositoryCreatedEventSubscriber.RepositoryCreatedEvent repositoryCreatedEvent = eventCaptor.getValue(); + assertThat(repositoryCreatedEvent.getPermission()).isEqualTo("repository:read:1"); + assertThat(repositoryCreatedEvent.getRepository()).isEqualTo(REPOSITORY.getNamespace() + "/" + REPOSITORY.getName()); + assertThat(repositoryCreatedEvent.getType()).isEqualTo(RepositoryCreatedEventSubscriber.RepositoryCreatedEvent.class.getSimpleName()); + assertThat(repositoryCreatedEvent.getCreator()).isEqualTo("Trillian"); + } + + @Test + void shouldNotFireMyEventWhenNotCreatedEvent() { + when(event.getEventType()).thenReturn(HandlerEventType.BEFORE_CREATE); + subscriber.handleEvent(event); + + verify(eventBus, never()).post(eventCaptor.capture()); + } + +} diff --git a/yarn.lock b/yarn.lock index 1f60f6bbed..61c247fdc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2047,17 +2047,16 @@ dependencies: "@types/node" ">= 8" -"@pmmmwh/react-refresh-webpack-plugin@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.1.3.tgz#bb6815315028087e6af4f96d063376880caf9c82" - integrity sha512-FJ8WzpGrao8Gz8KNAeU9dcTYr1RjbAGnXJMKKXTp4oAw494SqQK4HyGT8HMmIQt0ayukuP+A71w1oV8/5xSAWQ== +"@pmmmwh/react-refresh-webpack-plugin@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.2.0.tgz#e2a684d430f74ad6465680d9a5869f52f307ec1e" + integrity sha512-rjdNzcWroULJeD/Y0+eETy9LhM7c5tbPF+wqT5G680rwDkh3iothIPEqGAuEE2WJlXEaAq293aO6ySzsIU518Q== dependencies: ansi-html "^0.0.7" error-stack-parser "^2.0.4" html-entities "^1.2.1" lodash.debounce "^4.0.8" react-dev-utils "^9.1.0" - sockjs-client "^1.4.0" "@reach/router@^1.2.1": version "1.2.1" @@ -13389,7 +13388,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sockjs-client@1.4.0, sockjs-client@^1.4.0: +sockjs-client@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==