]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9940 implementation of api/editions/apply_license
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 17 Oct 2017 07:25:40 +0000 (09:25 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java
server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java
server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java

index 0d5c3b831ad63997a7db43ad1f712d4b61ebba1b..abdc676593dc0906811f5c8544f0d577313b7bb7 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.edition.ws;
 
-import java.util.Collections;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.server.ws.Request;
@@ -30,6 +29,7 @@ import org.sonar.server.edition.License;
 import org.sonar.server.edition.MutableEditionManagementState;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.license.LicenseCommit;
+import org.sonar.server.plugins.edition.EditionInstaller;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.ws.WsUtils;
 import org.sonarqube.ws.WsEditions;
@@ -41,17 +41,19 @@ public class ApplyLicenseAction implements EditionsWsAction {
 
   private final UserSession userSession;
   private final MutableEditionManagementState editionManagementState;
+  private final EditionInstaller editionInstaller;
   @CheckForNull
   private final LicenseCommit licenseCommit;
 
-  public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState) {
-    this(userSession, editionManagementState, null);
+  public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState, EditionInstaller editionInstaller) {
+    this(userSession, editionManagementState, editionInstaller, null);
   }
 
-  public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState,
+  public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState, EditionInstaller editionInstaller,
     @Nullable LicenseCommit licenseCommit) {
     this.userSession = userSession;
     this.editionManagementState = editionManagementState;
+    this.editionInstaller = editionInstaller;
     this.licenseCommit = licenseCommit;
   }
 
@@ -78,18 +80,21 @@ public class ApplyLicenseAction implements EditionsWsAction {
       throw BadRequestException.create("Can't apply a license when applying one is already in progress");
     }
 
-    String license = request.mandatoryParam(PARAM_LICENSE);
-    License newLicense = new License(license, Collections.emptyList(), license);
-    if (license.contains("manual")) {
-      editionManagementState.startManualInstall(newLicense);
-    } else if (license.contains("done")) {
+    String licenseParam = request.mandatoryParam(PARAM_LICENSE);
+    License newLicense = License.parse(licenseParam).orElseThrow(() -> BadRequestException.create("The license provided is invalid"));
+
+    if (!editionInstaller.requiresInstallationChange(newLicense.getPluginKeys())) {
+      editionManagementState.newEditionWithoutInstall(newLicense.getEditionKey());
       checkState(licenseCommit != null,
         "Can't decide edition does not require install if LicenseCommit instance is null. " +
           "License-manager plugin should be installed.");
-      licenseCommit.update(newLicense.getContent());
-      editionManagementState.newEditionWithoutInstall(newLicense.getEditionKey());
-    } else {
-      editionManagementState.startAutomaticInstall(newLicense);
+      licenseCommit.update(newLicense.getContent());    } else {
+      boolean online = editionInstaller.install(newLicense.getPluginKeys());
+      if (online) {
+        editionManagementState.startAutomaticInstall(newLicense);
+      } else {
+        editionManagementState.startManualInstall(newLicense);
+      }
     }
 
     WsUtils.writeProtobuf(buildResponse(), request, response);
index 966aa26c4cf80f020c2384772ee02d6ccd055084..8b1e4240e4ee2cbf5a88946813e471aeef78e830 100644 (file)
  */
 package org.sonar.server.plugins.edition;
 
+import com.google.common.base.Optional;
+import java.util.Collection;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.edition.MutableEditionManagementState;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.UpdateCenter;
 
 public class EditionInstaller {
   private final ReentrantLock lock = new ReentrantLock();
@@ -31,23 +37,35 @@ public class EditionInstaller {
   private final EditionPluginDownloader editionPluginDownloader;
   private final EditionPluginUninstaller editionPluginUninstaller;
   private final ServerPluginRepository pluginRepository;
+  private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
+  private final MutableEditionManagementState editionManagementState;
 
   public EditionInstaller(EditionPluginDownloader editionDownloader, EditionPluginUninstaller editionPluginUninstaller,
-    ServerPluginRepository pluginRepository, EditionInstallerExecutor executor) {
+    ServerPluginRepository pluginRepository, EditionInstallerExecutor executor, UpdateCenterMatrixFactory updateCenterMatrixFactory,
+    MutableEditionManagementState editionManagementState) {
     this.editionPluginDownloader = editionDownloader;
     this.editionPluginUninstaller = editionPluginUninstaller;
     this.pluginRepository = pluginRepository;
     this.executor = executor;
+    this.updateCenterMatrixFactory = updateCenterMatrixFactory;
+    this.editionManagementState = editionManagementState;
   }
 
-  public void install(Set<String> editionPlugins) {
+  /**
+   * Refreshes the update center, and submits in a executor a task to download all the needed plugins (asynchronously).
+   * If the update center is disabled or if we are offline, the task is not submitted and false is returned. 
+   * @return true if a task was submitted to perform the download, false if update center is unavailable.
+   * @throws IllegalStateException if an installation is already in progress
+   */
+  public boolean install(Set<String> editionPluginKeys) {
     if (lock.tryLock()) {
       try {
-        if (!requiresInstallationChange(editionPlugins)) {
-          return;
+        Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
+        if (!updateCenter.isPresent()) {
+          return false;
         }
-
-        executor.execute(() -> asyncInstall(editionPlugins));
+        executor.execute(() -> asyncInstall(editionPluginKeys, updateCenter.get()));
+        return true;
       } catch (RuntimeException e) {
         lock.unlock();
         throw e;
@@ -57,33 +75,42 @@ public class EditionInstaller {
     }
   }
 
+  public boolean isOffline() {
+    return !updateCenterMatrixFactory.getUpdateCenter(true).isPresent();
+  }
+
   public boolean requiresInstallationChange(Set<String> editionPluginKeys) {
-    return !pluginsToInstall(editionPluginKeys).isEmpty() || !pluginsToRemove(editionPluginKeys).isEmpty();
+    Map<String, PluginInfo> pluginInfosByKeys = pluginRepository.getPluginInfosByKeys();
+
+    return !pluginsToInstall(editionPluginKeys, pluginInfosByKeys.keySet()).isEmpty()
+      || !pluginsToRemove(editionPluginKeys, pluginInfosByKeys.values()).isEmpty();
   }
 
-  private void asyncInstall(Set<String> editionPluginKeys) {
+  private void asyncInstall(Set<String> editionPluginKeys, UpdateCenter updateCenter) {
+    Map<String, PluginInfo> pluginInfosByKeys = pluginRepository.getPluginInfosByKeys();
+    Set<String> pluginsToRemove = pluginsToRemove(editionPluginKeys, pluginInfosByKeys.values());
+    Set<String> pluginsToInstall = pluginsToInstall(editionPluginKeys, pluginInfosByKeys.keySet());
+
     try {
-      // TODO clean previously staged edition installations, or fail?
-      // editionPluginDownloader.cancelDownloads();
-      // editionPluginUninstaller.cancelUninstalls();
-      editionPluginDownloader.installEdition(pluginsToInstall(editionPluginKeys));
-      for (String pluginKey : pluginsToRemove(editionPluginKeys)) {
+      editionPluginDownloader.downloadEditionPlugins(pluginsToInstall, updateCenter);
+      for (String pluginKey : pluginsToRemove) {
         editionPluginUninstaller.uninstall(pluginKey);
       }
+      editionManagementState.automaticInstallReady();
     } finally {
       lock.unlock();
+      // TODO: catch exceptions and set error status
     }
   }
 
-  private Set<String> pluginsToInstall(Set<String> editionPluginKeys) {
-    Set<String> installedKeys = pluginRepository.getPluginInfosByKeys().keySet();
+  private Set<String> pluginsToInstall(Set<String> editionPluginKeys, Set<String> installedPluginKeys) {
     return editionPluginKeys.stream()
-      .filter(p -> !installedKeys.contains(p))
+      .filter(p -> !installedPluginKeys.contains(p))
       .collect(Collectors.toSet());
   }
 
-  private Set<String> pluginsToRemove(Set<String> editionPluginKeys) {
-    Set<String> installedCommercialPluginKeys = pluginRepository.getPluginInfos().stream()
+  private Set<String> pluginsToRemove(Set<String> editionPluginKeys, Collection<PluginInfo> installedPluginInfos) {
+    Set<String> installedCommercialPluginKeys = installedPluginInfos.stream()
       .filter(EditionInstaller::isSonarSourceCommercialPlugin)
       .map(PluginInfo::getKey)
       .collect(Collectors.toSet());
index db9943f00500001158149a7ce134e52e6a0b20c8..dff59fe316b3f933ecbf0753fa2066b1b6b2589c 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.plugins.edition;
 
-import com.google.common.base.Optional;
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
@@ -33,7 +32,6 @@ import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.util.FileUtils;
 import org.sonar.server.platform.ServerFileSystem;
-import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
@@ -45,37 +43,32 @@ public class EditionPluginDownloader {
   private static final Logger LOG = Loggers.get(EditionPluginDownloader.class);
   private static final String PLUGIN_EXTENSION = "jar";
 
-  private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
   private final Path tmpDir;
   private final Path downloadDir;
   private final HttpDownloader downloader;
 
-  public EditionPluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) {
+  public EditionPluginDownloader(HttpDownloader downloader, ServerFileSystem fileSystem) {
     this.downloadDir = fileSystem.getEditionDownloadedPluginsDir().toPath();
-    this.updateCenterMatrixFactory = updateCenterMatrixFactory;
     this.downloader = downloader;
     this.tmpDir = downloadDir.resolveSibling(downloadDir.getFileName() + "_tmp");
   }
 
-  public void installEdition(Set<String> pluginKeys) {
+  public void downloadEditionPlugins(Set<String> pluginKeys, UpdateCenter updateCenter) {
     try {
-      Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
-      if (updateCenter.isPresent()) {
-        Set<Release> pluginsToInstall = new HashSet<>();
-        for (String pluginKey : pluginKeys) {
-          pluginsToInstall.addAll(updateCenter.get().findInstallablePlugins(pluginKey, Version.create("")));
-        }
-
-        FileUtils.deleteQuietly(tmpDir);
-        Files.createDirectories(tmpDir);
+      Set<Release> pluginsToInstall = new HashSet<>();
+      for (String pluginKey : pluginKeys) {
+        pluginsToInstall.addAll(updateCenter.findInstallablePlugins(pluginKey, Version.create("")));
+      }
 
-        for (Release r : pluginsToInstall) {
-          download(r);
-        }
+      FileUtils.deleteQuietly(tmpDir);
+      Files.createDirectories(tmpDir);
 
-        FileUtils.deleteQuietly(downloadDir);
-        Files.move(tmpDir, downloadDir);
+      for (Release r : pluginsToInstall) {
+        download(r);
       }
+
+      FileUtils.deleteQuietly(downloadDir);
+      Files.move(tmpDir, downloadDir);
     } catch (Exception e) {
       FileUtils.deleteQuietly(tmpDir);
       throw new IllegalStateException("Failed to install edition", e);
index d894b5975e520a8f443fc557527e13856016567e..4f155e97a7c72967127bca0c18a8a68befb71714 100644 (file)
@@ -97,7 +97,7 @@ public class LicenseTest {
   }
 
   @Test
-  public void parse_is_empty_if_no_plugin() throws IOException {
+  public void parse_is_empty_if_license_has_no_plugin() throws IOException {
     Properties props = new Properties();
     props.setProperty("Plugins", "");
     props.setProperty("Edition", "dev");
@@ -110,6 +110,19 @@ public class LicenseTest {
     assertThat(license).isEmpty();
   }
 
+  @Test
+  public void parse_is_empty_if_license_has_no_edition_key() throws IOException {
+    Properties props = new Properties();
+    props.setProperty("Plugins", "plugin1,plugin2");
+    StringWriter writer = new StringWriter();
+    props.store(writer, "");
+
+    byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());
+
+    Optional<License> license = License.parse(new String(encoded));
+    assertThat(license).isEmpty();
+  }
+
   @Test
   public void verify_getters() {
     ImmutableSet<String> pluginKeys = ImmutableSet.of("a", "b", "c");
index f12b5fc7753b2de43e22f8d58ac808c176415539..bb11461f447e0da0b754b3bad59e77f308367c9f 100644 (file)
@@ -22,26 +22,41 @@ package org.sonar.server.edition.ws;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
 import java.util.Optional;
+import java.util.Properties;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.edition.EditionManagementState;
+import org.sonar.server.edition.EditionManagementState.PendingStatus;
+import org.sonar.server.edition.License;
 import org.sonar.server.edition.MutableEditionManagementState;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.license.LicenseCommit;
+import org.sonar.server.plugins.edition.EditionInstaller;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
 import org.sonar.test.JsonAssert;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsEditions;
+import org.sonarqube.ws.WsEditions.StatusResponse;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
 import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
@@ -49,15 +64,17 @@ import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE
 @RunWith(DataProviderRunner.class)
 public class ApplyLicenseActionTest {
   private static final String PARAM_LICENSE = "license";
+  private static final String PENDING_EDITION_NAME = "developer-edition";
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
   @Rule
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
 
+  private EditionInstaller editionInstaller = mock(EditionInstaller.class);
   private MutableEditionManagementState mutableEditionManagementState = mock(MutableEditionManagementState.class);
   private LicenseCommit licenseCommit = mock(LicenseCommit.class);
-  private ApplyLicenseAction underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState, licenseCommit);
+  private ApplyLicenseAction underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState, editionInstaller, licenseCommit);
   private WsActionTester actionTester = new WsActionTester(underTest);
 
   @Test
@@ -127,25 +144,109 @@ public class ApplyLicenseActionTest {
   }
 
   @Test
-  public void verify_example() {
+  public void request_fails_with_BadRequestException_if_license_is_invalid() {
     userSessionRule.logIn().setSystemAdministrator();
-    when(mutableEditionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
-    when(mutableEditionManagementState.getPendingEditionKey()).thenReturn(Optional.of("developer-edition"));
-    when(mutableEditionManagementState.getPendingInstallationStatus())
-        .thenReturn(NONE)
-        .thenReturn(AUTOMATIC_IN_PROGRESS);
+    TestRequest request = actionTester.newRequest()
+      .setParam(PARAM_LICENSE, "invalid");
+    when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
+
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("The license provided is invalid");
+    request.execute();
+  }
+
+  @Test
+  public void verify_example() throws IOException {
+    userSessionRule.logIn().setSystemAdministrator();
+    setPendingLicense(AUTOMATIC_IN_PROGRESS);
 
     TestRequest request = actionTester.newRequest()
-      .setParam(PARAM_LICENSE, "foo");
+      .setParam(PARAM_LICENSE, createLicenseParam("dev", "plugin1"));
 
     JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString());
   }
 
+  @Test
+  public void apply_without_need_to_install() throws IOException {
+    userSessionRule.logIn().setSystemAdministrator();
+    setPendingLicense(NONE);
+    when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(false);
+
+    TestRequest request = actionTester.newRequest()
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));
+
+    TestResponse response = request.execute();
+    assertResponse(response, PENDING_EDITION_NAME, "", NONE);
+    verify(mutableEditionManagementState).newEditionWithoutInstall(PENDING_EDITION_NAME);
+  }
+
+  @Test
+  public void apply_offline() throws IOException {
+    userSessionRule.logIn().setSystemAdministrator();
+    setPendingLicense(PendingStatus.MANUAL_IN_PROGRESS);
+    when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(true);
+    when(editionInstaller.install(Collections.singleton("plugin1"))).thenReturn(false);
+
+    TestRequest request = actionTester.newRequest()
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));
+
+    TestResponse response = request.execute();
+
+    assertResponse(response, PENDING_EDITION_NAME, "", PendingStatus.MANUAL_IN_PROGRESS);
+    verify(mutableEditionManagementState).startManualInstall(any(License.class));
+  }
+
+  @Test
+  public void apply_successfully_auto_installation() throws IOException {
+    userSessionRule.logIn().setSystemAdministrator();
+    setPendingLicense(PendingStatus.AUTOMATIC_IN_PROGRESS);
+    when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(true);
+    when(editionInstaller.install(Collections.singleton("plugin1"))).thenReturn(true);
+
+    TestRequest request = actionTester.newRequest()
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));
+
+    TestResponse response = request.execute();
+
+    assertResponse(response, PENDING_EDITION_NAME, "", PendingStatus.AUTOMATIC_IN_PROGRESS);
+    verify(mutableEditionManagementState).startAutomaticInstall(any(License.class));
+  }
+
+  private void assertResponse(TestResponse response, String expectedNextEditionKey, String expectedEditionKey,
+    PendingStatus expectedPendingStatus) throws IOException {
+    StatusResponse parsedResponse = WsEditions.StatusResponse.parseFrom(response.getInputStream());
+    assertThat(parsedResponse.getCurrentEditionKey()).isEqualTo(expectedEditionKey);
+    assertThat(parsedResponse.getNextEditionKey()).isEqualTo(expectedNextEditionKey);
+    assertThat(parsedResponse.getInstallationStatus()).isEqualTo(WsEditions.InstallationStatus.valueOf(expectedPendingStatus.toString()));
+  }
+
+  private void setPendingLicense(PendingStatus pendingStatus) {
+    when(mutableEditionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
+    when(mutableEditionManagementState.getPendingEditionKey()).thenReturn(Optional.of(PENDING_EDITION_NAME));
+    when(mutableEditionManagementState.getPendingInstallationStatus())
+      .thenReturn(NONE)
+      .thenReturn(pendingStatus);
+  }
+
+  private static String createLicenseParam(String editionKey, String... pluginKeys) throws IOException {
+    Properties props = new Properties();
+    props.setProperty("Plugins", String.join(",", pluginKeys));
+    props.setProperty("Edition", editionKey);
+    StringWriter writer = new StringWriter();
+    props.store(writer, "");
+
+    byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());
+    return new String(encoded, StandardCharsets.UTF_8);
+  }
+
   @DataProvider
   public static Object[][] notNonePendingInstallationStatuses() {
     return Arrays.stream(EditionManagementState.PendingStatus.values())
-        .filter(s -> s != NONE)
-        .map(s -> new Object[] {s})
-        .toArray(Object[][]::new);
+      .filter(s -> s != NONE)
+      .map(s -> new Object[] {s})
+      .toArray(Object[][]::new);
   }
 }
index 47a6a587c1709a4458b314945b793be1565e117a..ff988300f94282e78841e17d2e934ae277164a39 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.edition;
 
+import com.google.common.base.Optional;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -27,23 +28,29 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.edition.MutableEditionManagementState;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.UpdateCenter;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 public class EditionInstallerTest {
-  private static final String pluginKey = "key";
-  @Mock
-  private EditionPluginDownloader downloader;
-  @Mock
-  private EditionPluginUninstaller uninstaller;
-  @Mock
-  private ServerPluginRepository pluginRepository;
+  private static final String PLUGIN_KEY = "key";
+
+  private EditionPluginDownloader downloader = mock(EditionPluginDownloader.class);
+  private EditionPluginUninstaller uninstaller = mock(EditionPluginUninstaller.class);
+  private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class);
+  private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+  private UpdateCenter updateCenter = mock(UpdateCenter.class);
+  private MutableEditionManagementState editionManagementState = mock(MutableEditionManagementState.class);
 
   private EditionInstallerExecutor executor = new EditionInstallerExecutor() {
     public void execute(Runnable r) {
@@ -51,18 +58,17 @@ public class EditionInstallerTest {
     }
   };
 
-  private EditionInstaller installer;
+  private EditionInstaller installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor, updateCenterMatrixFactory, editionManagementState);
 
   @Before
-  public void before() {
-    MockitoAnnotations.initMocks(this);
-    installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor);
+  public void setUp() {
+    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
   }
 
   @Test
-  public void install() {
-    installer.install(Collections.singleton(pluginKey));
-    verify(downloader).installEdition(Collections.singleton(pluginKey));
+  public void launch_task_download_plugins() {
+    assertThat(installer.install(Collections.singleton(PLUGIN_KEY))).isTrue();
+    verify(downloader).downloadEditionPlugins(Collections.singleton(PLUGIN_KEY), updateCenter);
   }
 
   @Test
@@ -77,11 +83,70 @@ public class EditionInstallerTest {
     editionPlugins.add("p4");
     installer.install(editionPlugins);
 
-    verify(downloader).installEdition(Collections.singleton("p4"));
+    verify(editionManagementState).automaticInstallReady();
+    verify(downloader).downloadEditionPlugins(Collections.singleton("p4"), updateCenter);
     verify(uninstaller).uninstall("p2");
     verifyNoMoreInteractions(uninstaller);
     verifyNoMoreInteractions(downloader);
+  }
+
+  @Test
+  public void do_nothing_if_offline() {
+    mockPluginRepository(createPluginInfo("p1", true));
+    executor = mock(EditionInstallerExecutor.class);
+    when(updateCenterMatrixFactory.getUpdateCenter(true)).thenReturn(Optional.absent());
+    installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor, updateCenterMatrixFactory, editionManagementState);
+    assertThat(installer.install(Collections.singleton("p1"))).isFalse();
+
+    verifyZeroInteractions(executor);
+    verifyZeroInteractions(uninstaller);
+    verifyZeroInteractions(downloader);
+    verifyZeroInteractions(editionManagementState);
+  }
+
+  @Test
+  public void is_offline() {
+    when(updateCenterMatrixFactory.getUpdateCenter(true)).thenReturn(Optional.absent());
+    assertThat(installer.isOffline()).isTrue();
+  }
+
+  @Test
+  public void is_not_offline() {
+    assertThat(installer.isOffline()).isFalse();
+  }
+
+  @Test
+  public void requires_installation_change() {
+    PluginInfo commercial1 = createPluginInfo("p1", true);
+    PluginInfo commercial2 = createPluginInfo("p2", true);
+    PluginInfo open1 = createPluginInfo("p3", false);
+    mockPluginRepository(commercial1, commercial2, open1);
+
+    Set<String> editionPlugins = new HashSet<>();
+    editionPlugins.add("p1");
+    editionPlugins.add("p4");
+
+    assertThat(installer.requiresInstallationChange(editionPlugins)).isTrue();
+    verifyZeroInteractions(downloader);
+    verifyZeroInteractions(uninstaller);
+    verifyZeroInteractions(editionManagementState);
+  }
+
+  @Test
+  public void does_not_require_installation_change() {
+    PluginInfo commercial1 = createPluginInfo("p1", true);
+    PluginInfo commercial2 = createPluginInfo("p2", true);
+    PluginInfo open1 = createPluginInfo("p3", false);
+    mockPluginRepository(commercial1, commercial2, open1);
+
+    Set<String> editionPlugins = new HashSet<>();
+    editionPlugins.add("p1");
+    editionPlugins.add("p2");
 
+    assertThat(installer.requiresInstallationChange(editionPlugins)).isFalse();
+    verifyZeroInteractions(downloader);
+    verifyZeroInteractions(uninstaller);
+    verifyZeroInteractions(editionManagementState);
   }
 
   private void mockPluginRepository(PluginInfo... installedPlugins) {
index 6f3545e272d81071c487915d3ce49db3a96eb5ce..c2659beacfe8100dea74c764dec9710eb9d29770 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.plugins.edition;
 
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
@@ -31,11 +30,8 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.sonar.api.utils.HttpDownloader;
 import org.sonar.server.platform.ServerFileSystem;
-import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
@@ -43,7 +39,6 @@ import org.sonar.updatecenter.common.Version;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -52,14 +47,10 @@ import static org.mockito.Mockito.when;
 public class EditionPluginDownloaderTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
-  @Mock
-  private UpdateCenterMatrixFactory updateCenterMatrixFactory;
-  @Mock
-  private UpdateCenter updateCenter;
-  @Mock
-  private ServerFileSystem fs;
-  @Mock
-  private HttpDownloader httpDownloader;
+
+  private ServerFileSystem fs = mock(ServerFileSystem.class);
+  private HttpDownloader httpDownloader = mock(HttpDownloader.class);
+  private UpdateCenter updateCenter = mock(UpdateCenter.class);
 
   private File downloadDir;
   private File tmpDir;
@@ -67,12 +58,10 @@ public class EditionPluginDownloaderTest {
 
   @Before
   public void setUp() throws IOException {
-    MockitoAnnotations.initMocks(this);
-    downloadDir = temp.newFolder();
+    downloadDir = temp.newFolder("download");
     tmpDir = new File(downloadDir.getParentFile(), downloadDir.getName() + "_tmp");
-    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
     when(fs.getEditionDownloadedPluginsDir()).thenReturn(downloadDir);
-    downloader = new EditionPluginDownloader(updateCenterMatrixFactory, httpDownloader, fs);
+    downloader = new EditionPluginDownloader(httpDownloader, fs);
   }
 
   @Test
@@ -81,7 +70,7 @@ public class EditionPluginDownloaderTest {
       createRelease("plugin2", "1.0", "http://host/plugin2.jar"));
 
     when(updateCenter.findInstallablePlugins("plugins", Version.create(""))).thenReturn(releases);
-    downloader.installEdition(Collections.singleton("plugins"));
+    downloader.downloadEditionPlugins(Collections.singleton("plugins"), updateCenter);
 
     verify(httpDownloader).download(new URI("http://host/plugin1.jar"), new File(tmpDir, "plugin1.jar"));
     verify(httpDownloader).download(new URI("http://host/plugin2.jar"), new File(tmpDir, "plugin2.jar"));
@@ -100,14 +89,13 @@ public class EditionPluginDownloaderTest {
     when(updateCenter.findInstallablePlugins("plugins", Version.create(""))).thenReturn(releases);
 
     try {
-      downloader.installEdition(Collections.singleton("plugins"));
+      downloader.downloadEditionPlugins(Collections.singleton("plugins"), updateCenter);
       fail("expecting exception");
     } catch (IllegalStateException e) {
 
     }
 
     verify(httpDownloader).download(new URI("http://host/plugin1.jar"), new File(tmpDir, "plugin1.jar"));
-    verify(httpDownloader).download(new URI("http://host/plugin2.jar"), new File(tmpDir, "plugin2.jar"));
 
     assertThat(downloadDir.list()).isEmpty();
     assertThat(tmpDir).doesNotExist();