]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9951 finalize edition install at startup (mocked implem.)
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 12 Oct 2017 18:00:04 +0000 (20:00 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertyDbTester.java
server/sonar-server/src/main/java/org/sonar/server/edition/CommitPendingEditionOnStartup.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementCommitModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java
server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
server/sonar-server/src/test/java/org/sonar/server/edition/EditionManagementCommitModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java [new file with mode: 0644]

index b2bb7b7c5a45842af92f5960ea8dcc5cd0a6dd13..0fcc80db723317fb1c00e7fa463df4e6cc1353f1 100644 (file)
@@ -104,4 +104,10 @@ public class PropertyDbTester {
     dbSession.commit();
     return this;
   }
+
+  public PropertyDbTester insertEmptyInternal(String key) {
+    dbClient.internalPropertiesDao().saveAsEmpty(dbSession, key);
+    dbSession.commit();
+    return this;
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/CommitPendingEditionOnStartup.java b/server/sonar-server/src/main/java/org/sonar/server/edition/CommitPendingEditionOnStartup.java
new file mode 100644 (file)
index 0000000..42909c0
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.edition;
+
+import org.sonar.api.Startable;
+
+public class CommitPendingEditionOnStartup implements Startable {
+  private final MutableEditionManagementState editionManagementState;
+
+  public CommitPendingEditionOnStartup(MutableEditionManagementState editionManagementState) {
+    this.editionManagementState = editionManagementState;
+  }
+
+  @Override
+  public void start() {
+    EditionManagementState.PendingStatus status = editionManagementState.getPendingInstallationStatus();
+    switch (status) {
+      case NONE:
+        return;
+      case MANUAL_IN_PROGRESS:
+      case AUTOMATIC_READY:
+        // TODO save new license with plugin manager
+        editionManagementState.finalizeInstallation();
+        break;
+      case AUTOMATIC_IN_PROGRESS:
+        // FIXME temporary hack until download of edition is implemented
+        editionManagementState.automaticInstallReady();
+        editionManagementState.finalizeInstallation();
+        break;
+      default:
+        throw new IllegalStateException("Unsupported status " + status);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementCommitModule.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementCommitModule.java
new file mode 100644 (file)
index 0000000..6e417a4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.edition;
+
+import org.sonar.core.platform.Module;
+
+public class EditionManagementCommitModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      // TODO add cluster ready implementation when not running Standalone
+      // Edition management WebServices also depend on this class
+      StandaloneEditionManagementStateImpl.class,
+      CommitPendingEditionOnStartup.class);
+  }
+}
index 0a1ca5d30f767b55bbd8ccacfaef6eabe7d76215..1f22a1c3182df40254f54843800a8b65dc001e35 100644 (file)
@@ -28,7 +28,6 @@ public class EditionsWsModule extends Module {
   @Override
   protected void configureModule() {
     add(
-      StandaloneEditionManagementStateImpl.class,
       StatusAction.class,
       ApplyLicenseAction.class,
       EditionsWs.class);
index 1ca7eb3e62f38561af33ce58cf8d6d840daed825..5a714a4c7da2f2e6994a4dc04030b4e6b0a86ef6 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.edition;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Optional;
 import org.picocontainer.Startable;
@@ -37,14 +38,14 @@ import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANU
 import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
 
 public class StandaloneEditionManagementStateImpl implements MutableEditionManagementState, Startable {
-  private static final String CURRENT_EDITION_KEY = "sonar.editionManagement.currentEditionKey";
-  private static final String PENDING_INSTALLATION_STATUS = "sonar.editionManagement.pendingInstallationStatus";
-  private static final String PENDING_EDITION_KEY = "sonar.editionManagement.pendingEditionKey";
-  private static final String PENDING_LICENSE = "sonar.editionManagement.pendingLicense";
+  private static final String CURRENT_EDITION_KEY = "currentEditionKey";
+  private static final String PENDING_INSTALLATION_STATUS = "pendingInstallStatus";
+  private static final String PENDING_EDITION_KEY = "pendingEditionKey";
+  private static final String PENDING_LICENSE = "pendingLicense";
 
   private final DbClient dbClient;
   private String currentEditionKey;
-  private PendingStatus pendingInstallationStatus = NONE;
+  private PendingStatus pendingInstallationStatus;
   private String pendingEditionKey;
   private String pendingLicense;
 
@@ -62,12 +63,15 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
         .map(StandaloneEditionManagementStateImpl::emptyToNull)
         .orElse(null);
       this.pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty())
+        .map(StandaloneEditionManagementStateImpl::emptyToNull)
         .map(PendingStatus::valueOf)
         .orElse(NONE);
       this.pendingEditionKey = internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty())
         .map(StandaloneEditionManagementStateImpl::emptyToNull)
         .orElse(null);
-      this.pendingLicense = internalPropertyValues.getOrDefault(PENDING_LICENSE, empty()).orElse(null);
+      this.pendingLicense = internalPropertyValues.getOrDefault(PENDING_LICENSE, empty())
+        .map(StandaloneEditionManagementStateImpl::emptyToNull)
+        .orElse(null);
     }
   }
 
@@ -78,28 +82,33 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
 
   @Override
   public Optional<String> getCurrentEditionKey() {
+    ensureStarted();
     return Optional.ofNullable(currentEditionKey);
   }
 
   @Override
   public PendingStatus getPendingInstallationStatus() {
+    ensureStarted();
     return pendingInstallationStatus;
   }
 
   @Override
   public Optional<String> getPendingEditionKey() {
+    ensureStarted();
     return Optional.ofNullable(pendingEditionKey);
   }
 
   @Override
   public Optional<String> getPendingLicense() {
+    ensureStarted();
     return Optional.ofNullable(pendingLicense);
   }
 
   @Override
   public synchronized PendingStatus startAutomaticInstall(License license) {
+    ensureStarted();
     checkLicense(license);
-    changeStatusFromTo(NONE, AUTOMATIC_IN_PROGRESS);
+    changeStatusToFrom(AUTOMATIC_IN_PROGRESS, NONE);
     this.pendingLicense = license.getContent();
     this.pendingEditionKey = license.getEditionKey();
     persistProperties();
@@ -108,8 +117,9 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
 
   @Override
   public synchronized PendingStatus startManualInstall(License license) {
+    ensureStarted();
     checkLicense(license);
-    changeStatusFromTo(NONE, MANUAL_IN_PROGRESS);
+    changeStatusToFrom(MANUAL_IN_PROGRESS, NONE);
     this.pendingLicense = license.getContent();
     this.pendingEditionKey = license.getEditionKey();
     this.pendingInstallationStatus = MANUAL_IN_PROGRESS;
@@ -119,9 +129,10 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
 
   @Override
   public synchronized PendingStatus newEditionWithoutInstall(String newEditionKey) {
+    ensureStarted();
     requireNonNull(newEditionKey, "newEditionKey can't be null");
     checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty");
-    changeStatusFromTo(NONE, NONE);
+    changeStatusToFrom(NONE, NONE);
     this.currentEditionKey = newEditionKey;
     persistProperties();
     return this.pendingInstallationStatus;
@@ -129,15 +140,16 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
 
   @Override
   public synchronized PendingStatus automaticInstallReady() {
-    changeStatusFromTo(AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY);
+    ensureStarted();
+    changeStatusToFrom(AUTOMATIC_READY, AUTOMATIC_IN_PROGRESS);
     persistProperties();
     return this.pendingInstallationStatus;
   }
 
   @Override
   public synchronized PendingStatus finalizeInstallation() {
-    checkState(this.pendingInstallationStatus == AUTOMATIC_READY || this.pendingInstallationStatus == MANUAL_IN_PROGRESS,
-      "Can't finalize installation when state is %s", this.pendingInstallationStatus);
+    ensureStarted();
+    changeStatusToFrom(NONE, AUTOMATIC_READY, MANUAL_IN_PROGRESS);
 
     this.pendingInstallationStatus = NONE;
     this.currentEditionKey = this.pendingEditionKey;
@@ -147,10 +159,14 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag
     return this.pendingInstallationStatus;
   }
 
-  private void changeStatusFromTo(PendingStatus expectedStatus, PendingStatus newStatus) {
-    checkState(pendingInstallationStatus == expectedStatus,
-      "Can't move to {} when status is {} (should be {})",
-      newStatus, pendingInstallationStatus, expectedStatus);
+  private void ensureStarted() {
+    checkState(pendingInstallationStatus != null, "%s is not started", getClass().getSimpleName());
+  }
+
+  private void changeStatusToFrom(PendingStatus newStatus, PendingStatus... validPendingStatuses) {
+    checkState(Arrays.stream(validPendingStatuses).anyMatch(s -> s == pendingInstallationStatus),
+      "Can't move to %s when status is %s (should be any of %s)",
+      newStatus, pendingInstallationStatus, Arrays.toString(validPendingStatuses));
     this.pendingInstallationStatus = newStatus;
   }
 
index d7c3a3753b6c6bdcf6d3fa24444445a2ec1e65f9..8df49bfdf3aa4148b4e3074c0dad3959d09f7b33 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.platform.platformlevel;
 
 import org.sonar.api.utils.UriReader;
 import org.sonar.core.util.DefaultHttpDownloader;
+import org.sonar.server.edition.EditionManagementCommitModule;
 import org.sonar.server.organization.DefaultOrganizationProviderImpl;
 import org.sonar.server.organization.OrganizationFlagsImpl;
 import org.sonar.server.platform.ServerIdManager;
@@ -47,6 +48,7 @@ public class PlatformLevel3 extends PlatformLevel {
       UriReader.class,
       DefaultHttpDownloader.class,
       DefaultOrganizationProviderImpl.class,
-      OrganizationFlagsImpl.class);
+      OrganizationFlagsImpl.class,
+      EditionManagementCommitModule.class);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionManagementCommitModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionManagementCommitModuleTest.java
new file mode 100644 (file)
index 0000000..859f877
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.edition;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class EditionManagementCommitModuleTest {
+  private EditionManagementCommitModule underTest = new EditionManagementCommitModule();
+
+  @Test
+  public void verify_component_count() {
+    ComponentContainer container = new ComponentContainer();
+    underTest.configure(container);
+
+    assertThat(container.getPicoContainer().getComponentAdapters())
+      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2);
+  }
+
+}
index 116a4f4f2cdec3ba8707a05edeb1f77563721aba..c4fae543a7700e029bcbf32b20e87eb88b8e962f 100644 (file)
@@ -34,6 +34,6 @@ public class EditionsWsModuleTest {
     underTest.configure(container);
 
     assertThat(container.getPicoContainer().getComponentAdapters())
-      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4);
+      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java
new file mode 100644 (file)
index 0000000..e657f95
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.edition;
+
+import java.util.Collections;
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.server.edition.EditionManagementState.PendingStatus;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
+
+public class StandaloneEditionManagementStateImplTest {
+  private static final License LICENSE_WITHOUT_PLUGINS = new License(randomAlphanumeric(3), Collections.emptyList(), randomAlphanumeric(10));
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private final StandaloneEditionManagementStateImpl underTest = new StandaloneEditionManagementStateImpl(dbClient);
+
+  @Test
+  public void getCurrentEditionKey_fails_with_ISE_if_start_has_not_been_called() {
+    expectISENotStarted();
+
+    underTest.getCurrentEditionKey();
+  }
+
+  @Test
+  public void getCurrentEditionKey_returns_empty_when_internal_properties_table_is_empty() {
+    underTest.start();
+
+    assertThat(underTest.getCurrentEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void getCurrentEditionKey_returns_value_in_db_for_key_currentEditionKey() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("currentEditionKey", value);
+    underTest.start();
+
+    assertThat(underTest.getCurrentEditionKey()).contains(value);
+  }
+
+  @Test
+  public void getCurrentEditionKey_returns_empty_when_value_in_db_is_empty_for_key_currentEditionKey() {
+    dbTester.properties().insertEmptyInternal("currentEditionKey");
+    underTest.start();
+
+    assertThat(underTest.getCurrentEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void getPendingInstallationStatus_fails_with_ISE_if_start_has_not_been_called() {
+    expectISENotStarted();
+
+    underTest.getPendingInstallationStatus();
+  }
+
+  @Test
+  public void getPendingInstallationStatus_returns_NONE_when_internal_properties_table_is_empty() {
+    underTest.start();
+
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+  }
+
+  @Test
+  public void getPendingInstallationStatus_returns_value_in_db_for_key_pendingInstallStatus() {
+    PendingStatus value = PendingStatus.values()[new Random().nextInt(PendingStatus.values().length)];
+    dbTester.properties().insertInternal("pendingInstallStatus", value.name());
+    underTest.start();
+
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(value);
+  }
+
+  @Test
+  public void getPendingInstallationStatus_returns_NONE_when_value_in_db_is_empty_for_key_pendingInstallStatus() {
+    dbTester.properties().insertEmptyInternal("pendingInstallStatus");
+    underTest.start();
+
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+  }
+
+  @Test
+  public void start_fails_when_value_in_db_for_key_pendingInstallStatus_cannot_be_parsed_to_enum() {
+    String value = randomAlphanumeric(30);
+    dbTester.properties().insertInternal("pendingInstallStatus", value);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("No enum constant org.sonar.server.edition.EditionManagementState.PendingStatus." + value);
+
+    underTest.start();
+  }
+
+  @Test
+  public void getPendingEditionKey_fails_with_ISE_if_start_has_not_been_called() {
+    expectISENotStarted();
+
+    underTest.getPendingEditionKey();
+  }
+
+  @Test
+  public void getPendingEditionKey_returns_empty_when_internal_properties_table_is_empty() {
+    underTest.start();
+
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void getPendingEditionKey_returns_value_in_db_for_key_pendingEditionKey() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("pendingEditionKey", value);
+    underTest.start();
+
+    assertThat(underTest.getPendingEditionKey()).contains(value);
+  }
+
+  @Test
+  public void getPendingEditionKey_returns_empty_when_value_in_db_is_empty_for_key_pendingEditionKey() {
+    dbTester.properties().insertEmptyInternal("pendingEditionKey");
+    underTest.start();
+
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void getPendingLicense_fails_with_ISE_if_start_has_not_been_called() {
+    expectISENotStarted();
+
+    underTest.getPendingLicense();
+  }
+
+  @Test
+  public void getPendingLicense_returns_empty_when_internal_properties_table_is_empty() {
+    underTest.start();
+
+    assertThat(underTest.getPendingLicense()).isEmpty();
+  }
+
+  @Test
+  public void getPendingLicense_returns_empty_value_in_db_for_key_pendingLicense() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("pendingLicense", value);
+    underTest.start();
+
+    assertThat(underTest.getPendingLicense()).contains(value);
+  }
+
+  @Test
+  public void getPendingLicense_returns_empty_when_value_in_db_is_empty_for_key_pendingLicense() {
+    dbTester.properties().insertEmptyInternal("pendingLicense");
+    underTest.start();
+
+    assertThat(underTest.getPendingLicense()).isEmpty();
+  }
+
+  @Test
+  public void startAutomaticInstall_fails_with_ISE_if_not_started() {
+    expectISENotStarted();
+
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startAutomaticInstall_fails_with_NPE_if_license_is_null() {
+    underTest.start();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("license can't be null");
+
+    underTest.startAutomaticInstall(null);
+  }
+
+  @Test
+  public void startAutomaticInstall_sets_pending_fields_after_start() {
+    underTest.start();
+
+    PendingStatus newStatus = underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void startAutomaticInstall_sets_pending_fields_after_start_with_existing_currentEditionKey() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("currentEditionKey", value);
+    underTest.start();
+
+    PendingStatus newStatus = underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).contains(value);
+  }
+
+  @Test
+  public void startAutomaticInstall_sets_pending_fields_after_newEditionWithoutInstall() {
+    String value = randomAlphanumeric(10);
+    underTest.start();
+    underTest.newEditionWithoutInstall(value);
+
+    PendingStatus newStatus = underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(AUTOMATIC_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).contains(value);
+  }
+
+  @Test
+  public void startAutomaticInstall_fails_with_ISE_if_called_after_startAutomaticInstall() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to AUTOMATIC_IN_PROGRESS when status is AUTOMATIC_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startAutomaticInstall_fails_with_ISE_if_called_after_automaticInstallReady() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+    underTest.automaticInstallReady();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to AUTOMATIC_IN_PROGRESS when status is AUTOMATIC_READY (should be any of [NONE])");
+
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startAutomaticInstall_fails_with_ISE_if_called_after_manualInstall() {
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to AUTOMATIC_IN_PROGRESS when status is MANUAL_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startManualInstall_fails_with_ISE_if_not_started() {
+    expectISENotStarted();
+
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startManualInstall_fails_with_NPE_if_license_is_null() {
+    underTest.start();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("license can't be null");
+
+    underTest.startManualInstall(null);
+  }
+
+  @Test
+  public void startManualInstall_sets_pending_fields_after_start() {
+    underTest.start();
+
+    PendingStatus newStatus = underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void startManualInstall_sets_pending_fields_after_start_with_existing_currentEditionKey() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("currentEditionKey", value);
+    underTest.start();
+
+    PendingStatus newStatus = underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).contains(value);
+  }
+
+  @Test
+  public void startManualInstall_sets_pending_fields_after_newEditionWithoutInstall() {
+    String value = randomAlphanumeric(10);
+    underTest.start();
+    underTest.newEditionWithoutInstall(value);
+
+    PendingStatus newStatus = underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    assertThat(newStatus).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(MANUAL_IN_PROGRESS);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).contains(value);
+  }
+
+  @Test
+  public void startManualInstall_fails_with_ISE_if_called_after_startAutomaticInstall() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to MANUAL_IN_PROGRESS when status is AUTOMATIC_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startManualInstall_fails_with_ISE_if_called_after_automaticInstallReady() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+    underTest.automaticInstallReady();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to MANUAL_IN_PROGRESS when status is AUTOMATIC_READY (should be any of [NONE])");
+
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void startManualInstall_fails_with_ISE_if_called_after_manualInstall() {
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to MANUAL_IN_PROGRESS when status is MANUAL_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+  }
+
+  @Test
+  public void automaticInstallReady_fails_with_ISE_if_not_started() {
+    expectISENotStarted();
+
+    underTest.automaticInstallReady();
+  }
+
+  @Test
+  public void automaticInstallReady_fails_with_ISE_if_called() {
+    underTest.start();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to AUTOMATIC_READY when status is NONE (should be any of [AUTOMATIC_IN_PROGRESS])");
+
+    underTest.automaticInstallReady();
+  }
+
+  @Test
+  public void automaticInstallReady_fails_with_ISE_if_called_after_manualInstall() {
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to AUTOMATIC_READY when status is MANUAL_IN_PROGRESS (should be any of [AUTOMATIC_IN_PROGRESS])");
+
+    underTest.automaticInstallReady();
+  }
+
+  @Test
+  public void automaticInstallReady_after_startAutomaticInstall_changes_status_to_AUTOMATIC_READY_but_does_not_change_editions() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    PendingStatus newStatus = underTest.automaticInstallReady();
+
+    assertThat(newStatus).isEqualTo(AUTOMATIC_READY);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(AUTOMATIC_READY);
+    assertThat(underTest.getPendingEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+    assertThat(underTest.getPendingLicense()).contains(LICENSE_WITHOUT_PLUGINS.getContent());
+    assertThat(underTest.getCurrentEditionKey()).isEmpty();
+  }
+
+  @Test
+  public void newEditionWithoutInstall_fails_with_ISE_if_not_started() {
+    expectISENotStarted();
+
+    underTest.newEditionWithoutInstall(randomAlphanumeric(3));
+  }
+
+  @Test
+  public void newEditionWithoutInstall_fails_with_NPE_if_newEditionKey_is_null() {
+    underTest.start();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("newEditionKey can't be null");
+
+    underTest.newEditionWithoutInstall(null);
+  }
+
+  @Test
+  public void newEditionWithoutInstall_fails_with_IAE_if_newEditionKey_is_empty() {
+    underTest.start();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("newEditionKey can't be empty");
+
+    underTest.newEditionWithoutInstall("");
+  }
+
+  @Test
+  public void newEditionWithoutInstall_changes_current_edition() {
+    String newEditionKey = randomAlphanumeric(3);
+    underTest.start();
+
+    PendingStatus newStatus = underTest.newEditionWithoutInstall(newEditionKey);
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(newEditionKey);
+  }
+
+  @Test
+  public void newEditionWithoutInstall_overwrite_current_edition() {
+    String newEditionKey = randomAlphanumeric(3);
+    underTest.start();
+
+    PendingStatus newStatus = underTest.newEditionWithoutInstall(newEditionKey);
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(newEditionKey);
+  }
+
+  @Test
+  public void newEditionWithoutInstall_overwrites_from_previous_newEditionWithoutInstall() {
+    String newEditionKey1 = randomAlphanumeric(3);
+    String newEditionKey2 = randomAlphanumeric(3);
+    underTest.start();
+    underTest.newEditionWithoutInstall(newEditionKey1);
+
+    PendingStatus newStatus = underTest.newEditionWithoutInstall(newEditionKey2);
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(newEditionKey2);
+  }
+
+  @Test
+  public void newEditionWithoutInstall_fails_with_ISE_if_called_after_startAutomaticInstall() {
+    String newEditionKey = randomAlphanumeric(3);
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is AUTOMATIC_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.newEditionWithoutInstall(newEditionKey);
+  }
+
+  @Test
+  public void newEditionWithoutInstall_fails_with_ISE_if_called_after_startManualInstall() {
+    String newEditionKey = randomAlphanumeric(3);
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is MANUAL_IN_PROGRESS (should be any of [NONE])");
+
+    underTest.newEditionWithoutInstall(newEditionKey);
+  }
+
+  @Test
+  public void finalizeInstallation_fails_with_ISE_if_not_started() {
+    expectISENotStarted();
+
+    underTest.finalizeInstallation();
+  }
+
+  @Test
+  public void finalizeInstallation_fails_with_ISE_after_start() {
+    underTest.start();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is NONE (should be any of [AUTOMATIC_READY, MANUAL_IN_PROGRESS])");
+
+    underTest.finalizeInstallation();
+  }
+
+  @Test
+  public void finalizeInstallation_fails_with_ISE_after_newEditionWithoutInstall() {
+    underTest.start();
+    underTest.newEditionWithoutInstall(randomAlphanumeric(3));
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is NONE (should be any of [AUTOMATIC_READY, MANUAL_IN_PROGRESS])");
+
+    underTest.finalizeInstallation();
+  }
+
+  @Test
+  public void finalizeInstallation_fails_with_ISE_after_startAutomaticInstall() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is AUTOMATIC_IN_PROGRESS (should be any of [AUTOMATIC_READY, MANUAL_IN_PROGRESS])");
+
+    underTest.finalizeInstallation();
+  }
+
+  @Test
+  public void finalizeInstallation_set_new_edition_and_clear_pending_fields_after_manualInstallationReady() {
+    underTest.start();
+    underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS);
+    underTest.automaticInstallReady();
+
+    PendingStatus newStatus = underTest.finalizeInstallation();
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+  }
+
+  @Test
+  public void finalizeInstallation_set_new_edition_and_clear_pending_fields_after_startManualInstall() {
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    PendingStatus newStatus = underTest.finalizeInstallation();
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+  }
+
+  @Test
+  public void finalizeInstallation_overwrites_current_edition_and_clear_pending_fields_after_startManualInstall() {
+    String value = randomAlphanumeric(10);
+    dbTester.properties().insertInternal("currentEditionKey", value);
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+
+    PendingStatus newStatus = underTest.finalizeInstallation();
+
+    assertThat(newStatus).isEqualTo(NONE);
+    assertThat(underTest.getPendingInstallationStatus()).isEqualTo(NONE);
+    assertThat(underTest.getPendingEditionKey()).isEmpty();
+    assertThat(underTest.getPendingLicense()).isEmpty();
+    assertThat(underTest.getCurrentEditionKey()).contains(LICENSE_WITHOUT_PLUGINS.getEditionKey());
+  }
+
+  @Test
+  public void finalizeInstallation_fails_with_ISE_after_finalizeInstallation() {
+    underTest.start();
+    underTest.startManualInstall(LICENSE_WITHOUT_PLUGINS);
+    underTest.finalizeInstallation();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can't move to NONE when status is NONE (should be any of [AUTOMATIC_READY, MANUAL_IN_PROGRESS])");
+
+    underTest.finalizeInstallation();
+  }
+
+  private void expectISENotStarted() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("StandaloneEditionManagementStateImpl is not started");
+  }
+
+}