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
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;
// Plugins WS
pico.addSingleton(PluginWSCommons.class);
+ pico.addSingleton(PluginUpdateAggregator.class);
pico.addSingleton(InstalledPluginsWsAction.class);
pico.addSingleton(AvailablePluginsWsAction.class);
pico.addSingleton(UpdatesPluginsWsAction.class);
--- /dev/null
+/*
+ * 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<PluginUpdateAggregate> aggregate(@Nullable Collection<PluginUpdate> pluginUpdates) {
+ if (pluginUpdates == null || pluginUpdates.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Map<Plugin, PluginUpdateAggregateBuilder> 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<PluginUpdateAggregateBuilder, PluginUpdateAggregate> {
+ INSTANCE;
+
+ @Override
+ public PluginUpdateAggregate apply(@Nonnull PluginUpdateAggregateBuilder input) {
+ return input.build();
+ }
+ }
+
+ @VisibleForTesting
+ static class PluginUpdateAggregateBuilder {
+ private final Plugin plugin;
+
+ private final List<PluginUpdate> 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<PluginUpdate> updates;
+
+ protected PluginUpdateAggregate(PluginUpdateAggregateBuilder builder) {
+ this.plugin = builder.plugin;
+ this.updates = ImmutableList.copyOf(builder.updates);
+ }
+
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ public Collection<PluginUpdate> getUpdates() {
+ return updates;
+ }
+
+ }
+}
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;
public static final Ordering<PluginMetadata> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
.onResultOf(PluginMetadataToName.INSTANCE)
.compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE));
- public static final Comparator<PluginUpdate> 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<Plugin> 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<PluginUpdate> NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING)
+ .onResultOf(PluginUpdateToPlugin.INSTANCE);
public void writePluginMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) {
jsonWriter.beginObject();
jsonWriter.endObject();
}
+ /**
+ * Write an "update" object to the specified jsonwriter.
+ * <pre>
+ * "update": {
+ * "status": "COMPATIBLE",
+ * "requires": [
+ * {
+ * "key": "java",
+ * "name": "Java",
+ * "description": "SonarQube rule engine."
+ * }
+ * ]
+ * }
+ * </pre>
+ */
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.
+ * <pre>
+ * "status": "COMPATIBLE",
+ * "requires": [
+ * {
+ * "key": "java",
+ * "name": "Java",
+ * "description": "SonarQube rule engine."
+ * }
+ * ]
+ * </pre>
+ */
+ public void writeUpdateProperties(JsonWriter jsonWriter, PluginUpdate pluginUpdate) {
jsonWriter.prop(PROPERTY_STATUS, toJSon(pluginUpdate.getStatus()));
jsonWriter.name(ARRAY_REQUIRES);
jsonWriter.endObject();
}
jsonWriter.endArray();
-
- jsonWriter.endObject();
}
@VisibleForTesting
INSTANCE;
@Override
- public Artifact apply(@Nullable Release input) {
- if (input == null) {
- return null;
- }
+ public Artifact apply(@Nonnull Release input) {
return input.getArtifact();
}
}
}
}
- private enum PluginUpdateToKey implements Function<PluginUpdate, String> {
+ private enum PluginUpdateToPlugin implements Function<PluginUpdate, Plugin> {
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<PluginUpdate, String> {
+ private enum PluginToKey implements Function<Plugin, String> {
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<Plugin, String> {
+ INSTANCE;
+
+ @Override
+ public String apply(@Nonnull Plugin input) {
+ return input.getName();
}
}
}
*/
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.
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<PluginUpdateAggregate> NAME_KEY_PLUGIN_UPGRADE_AGGREGATE_ORDERING =
+ Ordering.from(PluginWSCommons.NAME_KEY_PLUGIN_ORDERING).onResultOf(PluginUpdateAggregateToPlugin.INSTANCE);
+ private static final Ordering<PluginUpdate> PLUGIN_UPDATE_BY_VERSION_ORDERING = Ordering.natural().onResultOf(new Function<PluginUpdate, String>() {
+ @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
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." +
"<br/>" +
"Update status values are: [COMPATIBLE, INCOMPATIBLE, REQUIRES_UPGRADE, DEPS_REQUIRE_UPGRADE]")
.setSince("5.2")
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<PluginUpdate> 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<PluginUpdate> 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<PluginUpdateAggregate> retrieveUpdatablePlugins() {
+ List<PluginUpdate> 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<PluginUpdateAggregate, Plugin> {
+ INSTANCE;
+
+ @Override
+ public Plugin apply(@Nonnull PluginUpdateAggregate input) {
+ return input.getPlugin();
+ }
+ }
}
"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",
"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
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;
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);
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
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;
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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.<PluginUpdate>emptyList())).isEmpty();
+ }
+
+ @Test
+ public void aggregates_groups_pluginUpdate_per_plugin_key() throws Exception {
+ Collection<PluginUpdateAggregator.PluginUpdateAggregate> 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<PluginUpdateAggregator.PluginUpdateAggregate> aggregates = underTest.aggregate(ImmutableList.of(
+ pluginUpdate1,
+ pluginUpdate2,
+ pluginUpdate3
+ ));
+
+ assertThat(aggregates).hasSize(1);
+ Collection<PluginUpdate> 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);
+ }
+}
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 {
}
@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
assertJson(response.outputAsString()).isSimilarTo(
"{" +
" \"plugins\": [" +
- " {" +
- " \"update\": {" +
- " \"status\": \"COMPATIBLE\"" +
- " }" +
+ " {" +
+ " \"updates\": [" +
+ " {" +
+ " \"status\": \"COMPATIBLE\"" +
+ " }" +
+ " ]" +
" }" +
" ]" +
"}"
}
@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")
" \"plugins\": [" +
" {" +
" \"key\": \"key0\"," +
- " \"name\": \"name0\"," +
- " }," +
- " {" +
- " \"key\": \"key1\"," +
- " \"name\": \"name1\"," +
+ " \"name\": \"name0\"" +
" }," +
" {" +
" \"key\": \"key1\"," +
- " \"name\": \"name2\"," +
+ " \"name\": \"name1\"" +
" }," +
" {" +
" \"key\": \"key2\"," +
- " \"name\": \"name2\"," +
+ " \"name\": \"name2\"" +
" }," +
" ]" +
"}"