diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index f0719226ce..71d269599d 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -73,6 +73,13 @@ public interface PluginManager { */ List getAvailable(); + /** + * Returns all updatable plugins. + * + * @return a list of updatable plugins. + */ + List getUpdatable(); + /** * Installs the plugin with the given name from the list of available plugins. * diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java index 5da00184ed..a301449c0d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java @@ -3,16 +3,15 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.InstalledPlugin; -import sonia.scm.plugin.PluginPermissions; +import sonia.scm.plugin.PluginManager; import java.util.List; import static de.otto.edison.hal.Embedded.embeddedBuilder; -import static de.otto.edison.hal.Link.*; +import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; import static java.util.stream.Collectors.toList; @@ -20,11 +19,13 @@ public class PluginDtoCollectionMapper { private final ResourceLinks resourceLinks; private final PluginDtoMapper mapper; + private final PluginManager manager; @Inject - public PluginDtoCollectionMapper(ResourceLinks resourceLinks, PluginDtoMapper mapper) { + public PluginDtoCollectionMapper(ResourceLinks resourceLinks, PluginDtoMapper mapper, PluginManager manager) { this.resourceLinks = resourceLinks; this.mapper = mapper; + this.manager = manager; } public HalRepresentation mapInstalled(List plugins, List availablePlugins) { @@ -46,7 +47,9 @@ public class PluginDtoCollectionMapper { Links.Builder linksBuilder = linkingTo() .with(Links.linkingTo().self(baseUrl).build()); - linksBuilder.single(link("update", resourceLinks.installedPluginCollection().update())); + if (!manager.getUpdatable().isEmpty()) { + linksBuilder.single(link("update", resourceLinks.installedPluginCollection().update())); + } return linksBuilder.build(); } 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 547a8285d3..53180e34eb 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -139,6 +139,14 @@ public class DefaultPluginManager implements PluginManager { .collect(Collectors.toList()); } + @Override + public List getUpdatable() { + return getInstalled() + .stream() + .filter(p -> isUpdatable(p.getDescriptor().getInformation().getName())) + .collect(Collectors.toList()); + } + private Predicate filterByName(String name) { return plugin -> name.equals(plugin.getDescriptor().getInformation().getName()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java index fb368d12f2..2d62534995 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java @@ -10,15 +10,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.AvailablePluginDescriptor; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; import java.net.URI; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -34,6 +38,9 @@ class PluginDtoCollectionMapperTest { @InjectMocks PluginDtoMapperImpl pluginDtoMapper; + @Mock + PluginManager manager; + Subject subject = mock(Subject.class); ThreadState subjectThreadState = new SubjectThreadState(subject); @@ -43,6 +50,11 @@ class PluginDtoCollectionMapperTest { ThreadContext.bind(subject); } + @BeforeEach + void mockPluginManager() { + lenient().when(manager.getUpdatable()).thenReturn(new ArrayList<>()); + } + @AfterEach public void unbindSubject() { ThreadContext.unbindSubject(); @@ -51,7 +63,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldMapInstalledPluginsWithoutUpdateWhenNoNewerVersionIsAvailable() { - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); HalRepresentation result = mapper.mapInstalled( singletonList(createInstalledPlugin("scm-some-plugin", "1")), @@ -66,7 +78,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldSetNewVersionInInstalledPluginWhenAvailableVersionIsNewer() { - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper,manager); HalRepresentation result = mapper.mapInstalled( singletonList(createInstalledPlugin("scm-some-plugin", "1")), @@ -80,7 +92,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldNotAddInstallLinkForNewVersionWhenNotPermitted() { when(subject.isPermitted("plugin:manage")).thenReturn(false); - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); HalRepresentation result = mapper.mapInstalled( singletonList(createInstalledPlugin("scm-some-plugin", "1")), @@ -93,7 +105,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldNotAddInstallLinkForNewVersionWhenInstallationIsPending() { when(subject.isPermitted("plugin:manage")).thenReturn(true); - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); AvailablePlugin availablePlugin = createAvailablePlugin("scm-some-plugin", "2"); when(availablePlugin.isPending()).thenReturn(true); @@ -108,7 +120,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldAddInstallLinkForNewVersionWhenPermitted() { when(subject.isPermitted("plugin:manage")).thenReturn(true); - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); HalRepresentation result = mapper.mapInstalled( singletonList(createInstalledPlugin("scm-some-plugin", "1")), @@ -121,7 +133,7 @@ class PluginDtoCollectionMapperTest { @Test void shouldSetInstalledPluginPendingWhenCorrespondingAvailablePluginIsPending() { when(subject.isPermitted("plugin:manage")).thenReturn(true); - PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); AvailablePlugin availablePlugin = createAvailablePlugin("scm-some-plugin", "2"); when(availablePlugin.isPending()).thenReturn(true);