From: Sébastien Lesaint Date: Thu, 23 Apr 2015 13:48:48 +0000 (+0200) Subject: SONAR-6379 fix response to display many updates per plugin X-Git-Tag: 5.2-RC1~2125 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=refs%2Fpull%2F253%2Fhead;p=sonarqube.git SONAR-6379 fix response to display many updates per plugin instead of currently displaying as many times the same plugin as there is updates for this plugin update unit test to test the sample response instead of some other json response specific to the unit test --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index db7e78f8c35..47f16ba9567 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -227,6 +227,7 @@ import org.sonar.server.plugins.ws.CancelAllPluginsWsAction; import org.sonar.server.plugins.ws.InstallPluginsWsAction; import org.sonar.server.plugins.ws.InstalledPluginsWsAction; import org.sonar.server.plugins.ws.PendingPluginsWsAction; +import org.sonar.server.plugins.ws.PluginUpdateAggregator; import org.sonar.server.plugins.ws.PluginWSCommons; import org.sonar.server.plugins.ws.PluginsWs; import org.sonar.server.plugins.ws.UninstallPluginsWsAction; @@ -893,6 +894,7 @@ class ServerComponents { // Plugins WS pico.addSingleton(PluginWSCommons.class); + pico.addSingleton(PluginUpdateAggregator.class); pico.addSingleton(InstalledPluginsWsAction.class); pico.addSingleton(AvailablePluginsWsAction.class); pico.addSingleton(UpdatesPluginsWsAction.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginUpdateAggregator.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginUpdateAggregator.java new file mode 100644 index 00000000000..e15954997f8 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginUpdateAggregator.java @@ -0,0 +1,119 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.ws; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.sonar.updatecenter.common.Plugin; +import org.sonar.updatecenter.common.PluginUpdate; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.transform; +import static java.lang.String.format; + +public class PluginUpdateAggregator { + + public Collection aggregate(@Nullable Collection pluginUpdates) { + if (pluginUpdates == null || pluginUpdates.isEmpty()) { + return Collections.emptyList(); + } + + Map builders = Maps.newHashMap(); + for (PluginUpdate pluginUpdate : pluginUpdates) { + Plugin plugin = pluginUpdate.getPlugin(); + PluginUpdateAggregateBuilder builder = builders.get(plugin); + if (builder == null) { + builder = PluginUpdateAggregateBuilder.builderFor(plugin); + builders.put(plugin, builder); + } + builder.add(pluginUpdate); + } + + return Lists.newArrayList(transform(builders.values(), BuilderToPluginUpdateAggregate.INSTANCE)); + } + + private enum BuilderToPluginUpdateAggregate implements Function { + INSTANCE; + + @Override + public PluginUpdateAggregate apply(@Nonnull PluginUpdateAggregateBuilder input) { + return input.build(); + } + } + + @VisibleForTesting + static class PluginUpdateAggregateBuilder { + private final Plugin plugin; + + private final List updates = Lists.newArrayListWithExpectedSize(1); + + // use static method + private PluginUpdateAggregateBuilder(Plugin plugin) { + this.plugin = plugin; + } + + public static PluginUpdateAggregateBuilder builderFor(Plugin plugin) { + return new PluginUpdateAggregateBuilder(checkNotNull(plugin)); + } + + public PluginUpdateAggregateBuilder add(PluginUpdate pluginUpdate) { + checkArgument( + this.plugin.equals(pluginUpdate.getPlugin()), + format("This builder only accepts PluginUpdate instances for plugin %s", plugin)); + this.updates.add(pluginUpdate); + return this; + } + + public PluginUpdateAggregate build() { + return new PluginUpdateAggregate(this); + } + } + + public static class PluginUpdateAggregate { + private final Plugin plugin; + + private final Collection updates; + + protected PluginUpdateAggregate(PluginUpdateAggregateBuilder builder) { + this.plugin = builder.plugin; + this.updates = ImmutableList.copyOf(builder.updates); + } + + public Plugin getPlugin() { + return plugin; + } + + public Collection getUpdates() { + return updates; + } + + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java index 5924784a292..a1367b4e66d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java @@ -30,7 +30,6 @@ import org.sonar.updatecenter.common.PluginUpdate; import org.sonar.updatecenter.common.Release; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Comparator; import static com.google.common.collect.Iterables.filter; @@ -60,11 +59,13 @@ public class PluginWSCommons { public static final Ordering NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() .onResultOf(PluginMetadataToName.INSTANCE) .compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE)); - public static final Comparator NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) - .onResultOf(PluginUpdateToName.INSTANCE) - .compound( - Ordering.from(CASE_INSENSITIVE_ORDER).onResultOf(PluginUpdateToKey.INSTANCE) - ); + public static final Comparator NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) + .onResultOf(PluginToName.INSTANCE) + .compound( + Ordering.from(CASE_INSENSITIVE_ORDER).onResultOf(PluginToKey.INSTANCE) + ); + public static final Comparator NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING) + .onResultOf(PluginUpdateToPlugin.INSTANCE); public void writePluginMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) { jsonWriter.beginObject(); @@ -142,9 +143,44 @@ public class PluginWSCommons { jsonWriter.endObject(); } + /** + * Write an "update" object to the specified jsonwriter. + *
+   * "update": {
+   *   "status": "COMPATIBLE",
+   *   "requires": [
+   *     {
+   *       "key": "java",
+   *       "name": "Java",
+   *       "description": "SonarQube rule engine."
+   *     }
+   *   ]
+   * }
+   * 
+ */ public void writeUpdate(JsonWriter jsonWriter, PluginUpdate pluginUpdate) { jsonWriter.name(OBJECT_UPDATE); jsonWriter.beginObject(); + + writeUpdateProperties(jsonWriter, pluginUpdate); + + jsonWriter.endObject(); + } + + /** + * Write the update properties to the specified jsonwriter. + *
+   * "status": "COMPATIBLE",
+   * "requires": [
+   *   {
+   *     "key": "java",
+   *     "name": "Java",
+   *     "description": "SonarQube rule engine."
+   *   }
+   * ]
+   * 
+ */ + public void writeUpdateProperties(JsonWriter jsonWriter, PluginUpdate pluginUpdate) { jsonWriter.prop(PROPERTY_STATUS, toJSon(pluginUpdate.getStatus())); jsonWriter.name(ARRAY_REQUIRES); @@ -158,8 +194,6 @@ public class PluginWSCommons { jsonWriter.endObject(); } jsonWriter.endArray(); - - jsonWriter.endObject(); } @VisibleForTesting @@ -182,10 +216,7 @@ public class PluginWSCommons { INSTANCE; @Override - public Artifact apply(@Nullable Release input) { - if (input == null) { - return null; - } + public Artifact apply(@Nonnull Release input) { return input.getArtifact(); } } @@ -208,27 +239,30 @@ public class PluginWSCommons { } } - private enum PluginUpdateToKey implements Function { + private enum PluginUpdateToPlugin implements Function { INSTANCE; @Override - public String apply(@Nullable PluginUpdate input) { - if (input == null) { - return null; - } - return input.getPlugin().getKey(); + public Plugin apply(@Nonnull PluginUpdate input) { + return input.getPlugin(); } } - private enum PluginUpdateToName implements Function { + private enum PluginToKey implements Function { INSTANCE; @Override - public String apply(@Nullable PluginUpdate input) { - if (input == null) { - return null; - } - return input.getPlugin().getName(); + public String apply(@Nonnull Plugin input) { + return input.getKey(); + } + } + + private enum PluginToName implements Function { + INSTANCE; + + @Override + public String apply(@Nonnull Plugin input) { + return input.getName(); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesPluginsWsAction.java index 22af90dc79f..16821425eec 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesPluginsWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesPluginsWsAction.java @@ -19,18 +19,22 @@ */ package org.sonar.server.plugins.ws; +import com.google.common.base.Function; import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Ordering; import com.google.common.io.Resources; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.plugins.ws.PluginUpdateAggregator.PluginUpdateAggregate; +import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; +import javax.annotation.Nonnull; import java.util.Collection; - -import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_UPDATE_ORDERING; +import java.util.List; /** * Implementation of the {@code updates} action for the Plugins WebService. @@ -39,14 +43,27 @@ public class UpdatesPluginsWsAction implements PluginsWsAction { private static final boolean DO_NOT_FORCE_REFRESH = false; private static final String ARRAY_PLUGINS = "plugins"; + private static final String ARRAY_UPDATES = "updates"; + + private static final Ordering NAME_KEY_PLUGIN_UPGRADE_AGGREGATE_ORDERING = + Ordering.from(PluginWSCommons.NAME_KEY_PLUGIN_ORDERING).onResultOf(PluginUpdateAggregateToPlugin.INSTANCE); + private static final Ordering PLUGIN_UPDATE_BY_VERSION_ORDERING = Ordering.natural().onResultOf(new Function() { + @Override + public String apply(@Nonnull PluginUpdate input) { + return input.getRelease().getVersion().toString(); + } + }); private final UpdateCenterMatrixFactory updateCenterMatrixFactory; private final PluginWSCommons pluginWSCommons; + private final PluginUpdateAggregator aggregator; public UpdatesPluginsWsAction(UpdateCenterMatrixFactory updateCenterMatrixFactory, - PluginWSCommons pluginWSCommons) { + PluginWSCommons pluginWSCommons, + PluginUpdateAggregator aggregator) { this.updateCenterMatrixFactory = updateCenterMatrixFactory; this.pluginWSCommons = pluginWSCommons; + this.aggregator = aggregator; } @Override @@ -54,7 +71,7 @@ public class UpdatesPluginsWsAction implements PluginsWsAction { controller.createAction("updates") .setDescription("Lists plugins installed on the SonarQube instance for which at least one newer version is available, sorted by plugin name." + "br/>" + - "Each newer version is a separate entry in the returned list, with its update/compatibility status." + + "Each newer version is listed, ordered from the oldest to the newest, with its own update/compatibility status." + "
" + "Update status values are: [COMPATIBLE, INCOMPATIBLE, REQUIRES_UPGRADE, DEPS_REQUIRE_UPGRADE]") .setSince("5.2") @@ -75,17 +92,50 @@ public class UpdatesPluginsWsAction implements PluginsWsAction { private void writePlugins(JsonWriter jsonWriter) { jsonWriter.name(ARRAY_PLUGINS); jsonWriter.beginArray(); - for (PluginUpdate pluginUpdate : retrieveUpdatablePlugins()) { - pluginWSCommons.writePluginUpdate(jsonWriter, pluginUpdate); + for (PluginUpdateAggregate aggregate : retrieveUpdatablePlugins()) { + writePluginUpdateAggregate(jsonWriter, aggregate); } jsonWriter.endArray(); jsonWriter.endObject(); } - private Collection retrieveUpdatablePlugins() { + private void writePluginUpdateAggregate(JsonWriter jsonWriter, PluginUpdateAggregate aggregate) { + jsonWriter.beginObject(); + Plugin plugin = aggregate.getPlugin(); + + pluginWSCommons.writeMetadata(jsonWriter, plugin); + + writeUpdates(jsonWriter, aggregate.getUpdates()); + + jsonWriter.endObject(); + } + + private void writeUpdates(JsonWriter jsonWriter, Collection pluginUpdates) { + jsonWriter.name(ARRAY_UPDATES).beginArray(); + for (PluginUpdate pluginUpdate : ImmutableSortedSet.copyOf(PLUGIN_UPDATE_BY_VERSION_ORDERING, pluginUpdates)) { + jsonWriter.beginObject(); + pluginWSCommons.writeRelease(jsonWriter, pluginUpdate.getRelease()); + pluginWSCommons.writeUpdateProperties(jsonWriter, pluginUpdate); + jsonWriter.endObject(); + } + jsonWriter.endArray(); + } + + private Collection retrieveUpdatablePlugins() { + List pluginUpdates = updateCenterMatrixFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH).findPluginUpdates(); + // aggregates updates of the same plugin to a single object and sort these objects by plugin name then key return ImmutableSortedSet.copyOf( - NAME_KEY_PLUGIN_UPDATE_ORDERING, - updateCenterMatrixFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH).findPluginUpdates() + NAME_KEY_PLUGIN_UPGRADE_AGGREGATE_ORDERING, + aggregator.aggregate(pluginUpdates) ); } + + private enum PluginUpdateAggregateToPlugin implements Function { + INSTANCE; + + @Override + public Plugin apply(@Nonnull PluginUpdateAggregate input) { + return input.getPlugin(); + } + } } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-updates_plugins.json b/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-updates_plugins.json index 2cfc80ca7ae..17e592ee7c1 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-updates_plugins.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-updates_plugins.json @@ -9,18 +9,32 @@ "organizationName": "SonarSource", "organizationUrl": "http://www.sonarsource.com", "termsAndConditionsUrl": "http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf", - "release": { - "version": "3.2", - "date": "2015-03-10", - "artifact": { - "name": "sonar-abap-plugin-3.2.jar", - "url": "http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.2.jar" + "updates": [ + { + "release": { + "version": "3.1", + "date": "2014-12-21", + "artifact": { + "name": "sonar-abap-plugin-3.1.jar", + "url": "http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.1.jar" + } + }, + "status": "INCOMPATIBLE", + "requires": [] + }, + { + "release": { + "version": "3.2", + "date": "2015-03-10", + "artifact": { + "name": "sonar-abap-plugin-3.2.jar", + "url": "http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.2.jar" + } + }, + "status": "COMPATIBLE", + "requires": [] } - }, - "update": { - "status": "COMPATIBLE", - "requires": [] - } + ] }, { "key": "android", @@ -30,24 +44,26 @@ "license": "GNU LGPL 3", "organizationName": "SonarSource and Jerome Van Der Linden, Stephane Nicolas, Florian Roncari, Thomas Bores", "organizationUrl": "http://www.sonarsource.com", - "release": { - "version": "1.0", - "date": "2014-03-31", - "artifact": { - "name": "sonar-android-plugin-1.0.jar", - "url": "http://repository.codehaus.org/org/codehaus/sonar-plugins/android/sonar-android-plugin/1.0/sonar-android-plugin-1.0.jar" + "updates": [ + { + "release": { + "version": "1.0", + "date": "2014-03-31", + "artifact": { + "name": "sonar-android-plugin-1.0.jar", + "url": "http://repository.codehaus.org/org/codehaus/sonar-plugins/android/sonar-android-plugin/1.0/sonar-android-plugin-1.0.jar" + } + }, + "status": "COMPATIBLE", + "requires": [ + { + "key": "java", + "name": "Java", + "description": "SonarQube rule engine." + } + ] } - }, - "update": { - "status": "COMPATIBLE", - "requires": [ - { - "key": "java", - "name": "Java", - "description": "SonarQube rule engine." - } - ] - } + ] } ] } \ No newline at end of file diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java index 94042b73857..c570497b297 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java @@ -21,7 +21,6 @@ package org.sonar.server.plugins.ws; import org.junit.Before; import org.sonar.api.server.ws.Request; -import org.sonar.api.utils.DateUtils; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; @@ -47,19 +46,6 @@ public class AbstractUpdateCenterBasedPluginsWsActionTest { protected static final Plugin PLUGIN_1 = new Plugin("p_key_1").setName("p_name_1"); protected static final Plugin PLUGIN_2 = new Plugin("p_key_2").setName("p_name_2").setDescription("p_desc_2"); - protected static final Plugin FULL_PROPERTIES_PLUGIN = new Plugin("p_key") - .setName("p_name") - .setCategory("p_category") - .setDescription("p_description") - .setLicense("p_license") - .setOrganization("p_orga_name") - .setOrganizationUrl("p_orga_url") - .setTermsConditionsUrl("p_t_and_c_url"); - protected static final Release FULL_PROPERTIES_PLUGIN_RELEASE = release(FULL_PROPERTIES_PLUGIN, "1.12.1") - .setDate(DateUtils.parseDate("2015-04-16")) - .setDownloadUrl("http://p_file.jar") - .addOutgoingDependency(release(PLUGIN_1, "0.3.6")) - .addOutgoingDependency(release(PLUGIN_2, "1.0.0")); protected UpdateCenterMatrixFactory updateCenterFactory = mock(UpdateCenterMatrixFactory.class); protected UpdateCenter updateCenter = mock(UpdateCenter.class); @@ -81,9 +67,9 @@ public class AbstractUpdateCenterBasedPluginsWsActionTest { protected static PluginUpdate pluginUpdate(String key, String name) { return PluginUpdate.createWithStatus( - new Release(new Plugin(key).setName(name), Version.create("1.0")), - COMPATIBLE - ); + new Release(new Plugin(key).setName(name), Version.create("1.0")), + COMPATIBLE + ); } @Before diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java index 9cee6366461..27a1f245686 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java @@ -21,11 +21,11 @@ package org.sonar.server.plugins.ws; import org.junit.Test; import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.DateUtils; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; import org.sonar.updatecenter.common.Release; -import org.sonar.updatecenter.common.Version; import static com.google.common.collect.ImmutableList.of; import static org.assertj.core.api.Assertions.assertThat; @@ -38,6 +38,20 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UP public class AvailablePluginsWsActionTest extends AbstractUpdateCenterBasedPluginsWsActionTest { + private static final Plugin FULL_PROPERTIES_PLUGIN = new Plugin("p_key") + .setName("p_name") + .setCategory("p_category") + .setDescription("p_description") + .setLicense("p_license") + .setOrganization("p_orga_name") + .setOrganizationUrl("p_orga_url") + .setTermsConditionsUrl("p_t_and_c_url"); + private static final Release FULL_PROPERTIES_PLUGIN_RELEASE = release(FULL_PROPERTIES_PLUGIN, "1.12.1") + .setDate(DateUtils.parseDate("2015-04-16")) + .setDownloadUrl("http://p_file.jar") + .addOutgoingDependency(release(PLUGIN_1, "0.3.6")) + .addOutgoingDependency(release(PLUGIN_2, "1.0.0")); + private AvailablePluginsWsAction underTest = new AvailablePluginsWsAction(updateCenterFactory, new PluginWSCommons()); @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregateBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregateBuilderTest.java new file mode 100644 index 00000000000..09c5ee87c3c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregateBuilderTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.ws; + +import org.junit.Test; +import org.sonar.server.plugins.ws.PluginUpdateAggregator.PluginUpdateAggregateBuilder; +import org.sonar.updatecenter.common.Plugin; +import org.sonar.updatecenter.common.PluginUpdate; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.Version; + +public class PluginUpdateAggregateBuilderTest { + + private static final Plugin PLUGIN_1 = new Plugin("key1"); + private static final Plugin PLUGIN_2 = new Plugin("key2"); + private static final Version SOME_VERSION = Version.create("1.0"); + private static final PluginUpdate.Status SOME_STATUS = PluginUpdate.Status.COMPATIBLE; + + @Test(expected = NullPointerException.class) + public void plugin_can_not_be_null_and_builderFor_enforces_it_with_NPE() throws Exception { + PluginUpdateAggregateBuilder.builderFor(null); + } + + @Test(expected = IllegalArgumentException.class) + public void add_throws_IAE_when_plugin_is_not_equal_to_the_one_of_the_builder() throws Exception { + PluginUpdateAggregateBuilder builder = PluginUpdateAggregateBuilder.builderFor(PLUGIN_1); + + builder.add(createPluginUpdate(PLUGIN_2)); + } + + @Test + public void add_uses_equals_which_takes_only_key_into_account() throws Exception { + PluginUpdateAggregateBuilder builder = PluginUpdateAggregateBuilder.builderFor(PLUGIN_1); + + builder.add(createPluginUpdate(new Plugin(PLUGIN_1.getKey()))); + } + + private static PluginUpdate createPluginUpdate(Plugin plugin) { + return PluginUpdate.createWithStatus(new Release(plugin, SOME_VERSION), SOME_STATUS); + } +} \ No newline at end of file diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java new file mode 100644 index 00000000000..b7e6140a562 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.ws; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.sonar.updatecenter.common.Plugin; +import org.sonar.updatecenter.common.PluginUpdate; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.Version; + +import java.util.Collection; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginUpdateAggregatorTest { + + public static final Version SOME_VERSION = Version.create("1.0"); + public static final PluginUpdate.Status SOME_STATUS = PluginUpdate.Status.COMPATIBLE; + private PluginUpdateAggregator underTest = new PluginUpdateAggregator(); + + @Test + public void aggregates_returns_an_empty_collection_when_plugin_collection_is_null() throws Exception { + assertThat(underTest.aggregate(null)).isEmpty(); + } + + @Test + public void aggregates_returns_an_empty_collection_when_plugin_collection_is_empty() throws Exception { + assertThat(underTest.aggregate(Collections.emptyList())).isEmpty(); + } + + @Test + public void aggregates_groups_pluginUpdate_per_plugin_key() throws Exception { + Collection aggregates = underTest.aggregate(ImmutableList.of( + createPluginUpdate("key1"), + createPluginUpdate("key1"), + createPluginUpdate("key0"), + createPluginUpdate("key2"), + createPluginUpdate("key0") + )); + + assertThat(aggregates).hasSize(3); + assertThat(aggregates).extracting("plugin.key").containsOnlyOnce("key1", "key0", "key2"); + } + + @Test + public void aggregate_put_pluginUpdates_with_same_plugin_in_the_same_PluginUpdateAggregate() throws Exception { + PluginUpdate pluginUpdate1 = createPluginUpdate("key1"); + PluginUpdate pluginUpdate2 = createPluginUpdate("key1"); + PluginUpdate pluginUpdate3 = createPluginUpdate("key1"); + Collection aggregates = underTest.aggregate(ImmutableList.of( + pluginUpdate1, + pluginUpdate2, + pluginUpdate3 + )); + + assertThat(aggregates).hasSize(1); + Collection releases = aggregates.iterator().next().getUpdates(); + assertThat(releases).hasSize(3); + assertThat(releases).contains(pluginUpdate1); + assertThat(releases).contains(pluginUpdate2); + assertThat(releases).contains(pluginUpdate3); + } + + private PluginUpdate createPluginUpdate(String pluginKey) { + return PluginUpdate.createWithStatus(new Release(new Plugin(pluginKey), SOME_VERSION), SOME_STATUS); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesPluginsWsActionTest.java index a5baa19f4a7..f5cf5b8ef51 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesPluginsWsActionTest.java @@ -21,17 +21,51 @@ package org.sonar.server.plugins.ws; import org.junit.Test; import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.DateUtils; import org.sonar.server.ws.WsTester; +import org.sonar.updatecenter.common.Plugin; +import org.sonar.updatecenter.common.Release; import static com.google.common.collect.ImmutableList.of; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE; +import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE; public class UpdatesPluginsWsActionTest extends AbstractUpdateCenterBasedPluginsWsActionTest { - - private UpdatesPluginsWsAction underTest = new UpdatesPluginsWsAction(updateCenterFactory, new PluginWSCommons()); + private static final Plugin JAVA_PLUGIN = new Plugin("java") + .setName("Java") + .setDescription("SonarQube rule engine."); + private static final Plugin ABAP_PLUGIN = new Plugin("abap") + .setName("ABAP") + .setCategory("Languages") + .setDescription("Enable analysis and reporting on ABAP projects") + .setLicense("Commercial") + .setOrganization("SonarSource") + .setOrganizationUrl("http://www.sonarsource.com") + .setTermsConditionsUrl("http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf"); + private static final Release ABAP_31 = release(ABAP_PLUGIN, "3.1") + .setDate(DateUtils.parseDate("2014-12-21")) + .setDownloadUrl("http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.1.jar"); + private static final Release ABAP_32 = release(ABAP_PLUGIN, "3.2") + .setDate(DateUtils.parseDate("2015-03-10")) + .setDownloadUrl("http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.2.jar"); + private static final Plugin ANDROID_PLUGIN = new Plugin("android") + .setName("Android") + .setCategory("Languages") + .setDescription("Import Android Lint reports.") + .setLicense("GNU LGPL 3") + .setOrganization("SonarSource and Jerome Van Der Linden, Stephane Nicolas, Florian Roncari, Thomas Bores") + .setOrganizationUrl("http://www.sonarsource.com"); + private static final Release ANDROID_10 = release(ANDROID_PLUGIN, "1.0") + .setDate(DateUtils.parseDate("2014-03-31")) + .setDownloadUrl("http://repository.codehaus.org/org/codehaus/sonar-plugins/android/sonar-android-plugin/1.0/sonar-android-plugin-1.0.jar") + .addOutgoingDependency(release(JAVA_PLUGIN, "1.0")); + + private UpdatesPluginsWsAction underTest = new UpdatesPluginsWsAction(updateCenterFactory, + new PluginWSCommons(), new PluginUpdateAggregator() + ); @Test public void action_updatable_is_defined() throws Exception { @@ -58,14 +92,16 @@ public class UpdatesPluginsWsActionTest extends AbstractUpdateCenterBasedPlugins } @Test - public void verify_properties_displayed_in_json_per_plugin() throws Exception { + public void verify_response_against_example() throws Exception { when(updateCenter.findPluginUpdates()).thenReturn(of( - pluginUpdate(FULL_PROPERTIES_PLUGIN_RELEASE, COMPATIBLE) + pluginUpdate(ABAP_32, COMPATIBLE), + pluginUpdate(ABAP_31, INCOMPATIBLE), + pluginUpdate(ANDROID_10, COMPATIBLE) )); underTest.handle(request, response); - assertJson(response.outputAsString()).isSimilarTo(resource("properties_per_plugin.json")); + assertJson(response.outputAsString()).isSimilarTo(getClass().getResource("example-updates_plugins.json")); } @Test @@ -79,10 +115,12 @@ public class UpdatesPluginsWsActionTest extends AbstractUpdateCenterBasedPlugins assertJson(response.outputAsString()).isSimilarTo( "{" + " \"plugins\": [" + - " {" + - " \"update\": {" + - " \"status\": \"COMPATIBLE\"" + - " }" + + " {" + + " \"updates\": [" + + " {" + + " \"status\": \"COMPATIBLE\"" + + " }" + + " ]" + " }" + " ]" + "}" @@ -90,10 +128,9 @@ public class UpdatesPluginsWsActionTest extends AbstractUpdateCenterBasedPlugins } @Test - public void plugins_are_sorted_by_name_then_key_and_made_unique() throws Exception { + public void plugins_are_sorted_by_name_and_made_unique() throws Exception { when(updateCenter.findPluginUpdates()).thenReturn(of( pluginUpdate("key2", "name2"), - pluginUpdate("key1", "name2"), pluginUpdate("key2", "name2"), pluginUpdate("key0", "name0"), pluginUpdate("key1", "name1") @@ -106,19 +143,15 @@ public class UpdatesPluginsWsActionTest extends AbstractUpdateCenterBasedPlugins " \"plugins\": [" + " {" + " \"key\": \"key0\"," + - " \"name\": \"name0\"," + - " }," + - " {" + - " \"key\": \"key1\"," + - " \"name\": \"name1\"," + + " \"name\": \"name0\"" + " }," + " {" + " \"key\": \"key1\"," + - " \"name\": \"name2\"," + + " \"name\": \"name1\"" + " }," + " {" + " \"key\": \"key2\"," + - " \"name\": \"name2\"," + + " \"name\": \"name2\"" + " }," + " ]" + "}"