Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2019-06-13 06:34:52 +02:00
37 changed files with 1377 additions and 258 deletions

View File

@@ -0,0 +1,224 @@
package sonia.scm.update;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.update.repository.MigrationStrategy;
import sonia.scm.update.repository.MigrationStrategyDao;
import sonia.scm.update.repository.V1Repository;
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MigrationWizardServletTest {
@Mock
XmlRepositoryV1UpdateStep updateStep;
@Mock
MigrationStrategyDao migrationStrategyDao;
@Mock
HttpServletRequest request;
@Mock
HttpServletResponse response;
String renderedTemplateName;
Map<String, Object> renderedModel;
MigrationWizardServlet servlet;
@BeforeEach
void initServlet() {
servlet = new MigrationWizardServlet(updateStep, migrationStrategyDao) {
@Override
void respondWithTemplate(HttpServletResponse resp, Map<String, Object> model, String templateName) {
renderedTemplateName = templateName;
renderedModel = model;
}
};
}
@Test
void shouldUseRepositoryTypeAsNamespaceForNamesWithSingleElement() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "simple"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("namespace")
.contains("git");
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("name")
.contains("simple");
}
@Test
void shouldUseDirectoriesForNamespaceAndNameForNamesWithTwoElements() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "two/dirs"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("namespace")
.contains("two");
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("name")
.contains("dirs");
}
@Test
void shouldUseDirectoriesForNamespaceAndConcatenatedNameForNamesWithMoreThanTwoElements() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "more/than/two/dirs"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("namespace")
.contains("more");
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("name")
.contains("than_two_dirs");
}
@Test
void shouldUseTypeAndNameAsPath() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("path")
.contains("git/name");
}
@Test
void shouldKeepId() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("id")
.contains("id");
}
@Test
void shouldNotBeInvalidAtFirstRequest() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
servlet.doGet(request, response);
assertThat(renderedModel.get("validationErrorsFound")).isEqualTo(false);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("namespaceInvalid")
.contains(false);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("nameInvalid")
.contains(false);
}
@Test
void shouldValidateNamespaceAndNameOnPost() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
doReturn("invalid namespace").when(request).getParameter("namespace-id");
doReturn("invalid name").when(request).getParameter("name-id");
doReturn("COPY").when(request).getParameter("strategy-id");
servlet.doPost(request, response);
assertThat(renderedModel.get("validationErrorsFound")).isEqualTo(true);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("namespaceInvalid")
.contains(true);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("nameInvalid")
.contains(true);
}
@Test
void shouldKeepSelectedMigrationStrategy() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
doReturn("we need an").when(request).getParameter("namespace-id");
doReturn("error for this test").when(request).getParameter("name-id");
doReturn("INLINE").when(request).getParameter("strategy-id");
servlet.doPost(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("selectedStrategy")
.contains(MigrationStrategy.INLINE);
}
@Test
void shouldUseCopyWithoutMigrationStrategy() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
doReturn("we need an").when(request).getParameter("namespace-id");
doReturn("error for this test").when(request).getParameter("name-id");
doReturn("").when(request).getParameter("strategy-id");
servlet.doPost(request, response);
assertThat(renderedModel.get("repositories"))
.asList()
.extracting("selectedStrategy")
.contains(MigrationStrategy.COPY);
}
@Test
void shouldStoreValidMigration() {
when(updateStep.getRepositoriesWithoutMigrationStrategies()).thenReturn(
Collections.singletonList(new V1Repository("id", "git", "name"))
);
doReturn("namespace").when(request).getParameter("namespace-id");
doReturn("name").when(request).getParameter("name-id");
doReturn("COPY").when(request).getParameter("strategy-id");
servlet.doPost(request, response);
verify(migrationStrategyDao).set("id", MigrationStrategy.COPY, "namespace", "name");
}
}

View File

@@ -43,18 +43,18 @@ class CopyMigrationStrategyTest {
void mockLocationResolver(@TempDirectory.TempDir Path tempDir) {
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
when(instanceMock.getLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
}
@Test
void shouldUseStandardDirectory(@TempDirectory.TempDir Path tempDir) {
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
}
@Test
void shouldCopyDataDirectory(@TempDirectory.TempDir Path tempDir) {
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
assertThat(target.resolve("data")).exists();
Path originalDataDir = tempDir
.resolve("repositories")

View File

@@ -7,11 +7,14 @@ import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import java.io.IOException;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(TempDirectory.class)
@@ -20,9 +23,14 @@ class InlineMigrationStrategyTest {
@Mock
SCMContextProvider contextProvider;
@Mock
PathBasedRepositoryLocationResolver locationResolver;
@Mock
RepositoryLocationResolver.RepositoryLocationResolverInstance locationResolverInstance;
@BeforeEach
void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
when(locationResolver.forClass(Path.class)).thenReturn(locationResolverInstance);
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
}
@@ -33,13 +41,14 @@ class InlineMigrationStrategyTest {
@Test
void shouldUseExistingDirectory(@TempDirectory.TempDir Path tempDir) {
Path target = new InlineMigrationStrategy(contextProvider).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
Path target = new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
assertThat(target).isEqualTo(resolveOldDirectory(tempDir));
verify(locationResolverInstance).setLocation("b4f-a9f0-49f7-ad1f-37d3aae1c55f", target);
}
@Test
void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
new InlineMigrationStrategy(contextProvider).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
assertThat(resolveOldDirectory(tempDir).resolve("data")).exists();
}

View File

@@ -11,8 +11,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.update.repository.MigrationStrategy;
import sonia.scm.update.repository.MigrationStrategyDao;
import javax.xml.bind.JAXBException;
import java.nio.file.Path;
@@ -37,23 +35,31 @@ class MigrationStrategyDaoTest {
}
@Test
void shouldReturnEmptyOptionalWhenStoreIsEmpty() throws JAXBException {
void shouldReturnEmptyOptionalWhenStoreIsEmpty() {
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
Optional<MigrationStrategy> strategy = dao.get("any");
Optional<RepositoryMigrationPlan.RepositoryMigrationEntry> entry = dao.get("any");
Assertions.assertThat(strategy).isEmpty();
Assertions.assertThat(entry).isEmpty();
}
@Test
void shouldReturnNewValue() throws JAXBException {
void shouldReturnNewValue() {
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
dao.set("id", INLINE);
dao.set("id", INLINE, "space", "name");
Optional<MigrationStrategy> strategy = dao.get("id");
Optional<RepositoryMigrationPlan.RepositoryMigrationEntry> entry = dao.get("id");
Assertions.assertThat(strategy).contains(INLINE);
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getDataMigrationStrategy)
.contains(INLINE);
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getNewNamespace)
.contains("space");
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getNewName)
.contains("name");
}
@Nested
@@ -62,16 +68,24 @@ class MigrationStrategyDaoTest {
void initExistingDatabase() throws JAXBException {
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
dao.set("id", INLINE);
dao.set("id", INLINE, "space", "name");
}
@Test
void shouldFindExistingValue() throws JAXBException {
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
Optional<MigrationStrategy> strategy = dao.get("id");
Optional<RepositoryMigrationPlan.RepositoryMigrationEntry> entry = dao.get("id");
Assertions.assertThat(strategy).contains(INLINE);
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getDataMigrationStrategy)
.contains(INLINE);
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getNewNamespace)
.contains("space");
Assertions.assertThat(entry)
.map(RepositoryMigrationPlan.RepositoryMigrationEntry::getNewName)
.contains("name");
}
}
}

View File

@@ -3,10 +3,13 @@ package sonia.scm.update.repository;
import com.google.inject.Injector;
import sonia.scm.update.repository.MigrationStrategy.Instance;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import static java.util.Optional.of;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -20,6 +23,13 @@ class MigrationStrategyMock {
.thenAnswer(
invocationOnMock -> mocks.computeIfAbsent(invocationOnMock.getArgument(0), key -> mock((Class<Instance>) key))
);
for (MigrationStrategy strategy : MigrationStrategy.values()) {
MigrationStrategy.Instance strategyMock = mock(strategy.getImplementationClass());
when(strategyMock.migrate(any(), any(), any())).thenReturn(of(Paths.get("")));
lenient().when(mock.getInstance((Class<MigrationStrategy.Instance>) strategy.getImplementationClass())).thenReturn(strategyMock);
}
return mock;
}
}

View File

@@ -40,18 +40,18 @@ class MoveMigrationStrategyTest {
void mockLocationResolver(@TempDirectory.TempDir Path tempDir) {
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
when(instanceMock.getLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
}
@Test
void shouldUseStandardDirectory(@TempDirectory.TempDir Path tempDir) {
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
}
@Test
void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
assertThat(target.resolve("data")).exists();
Path originalDataDir = tempDir
.resolve("repositories")

View File

@@ -11,6 +11,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.xml.XmlRepositoryDAO;
@@ -33,11 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.update.repository.MigrationStrategy.COPY;
import static sonia.scm.update.repository.MigrationStrategy.INLINE;
import static sonia.scm.update.repository.MigrationStrategy.MOVE;
@ExtendWith(MockitoExtension.class)
@@ -89,9 +89,14 @@ class XmlRepositoryV1UpdateStepTest {
@BeforeEach
void createMigrationPlan() {
lenient().when(migrationStrategyDao.get("3b91caa5-59c3-448f-920b-769aaa56b761")).thenReturn(of(MOVE));
lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenReturn(of(COPY));
lenient().when(migrationStrategyDao.get("454972da-faf9-4437-b682-dc4a4e0aa8eb")).thenReturn(of(INLINE));
Answer<Object> planAnswer = invocation -> {
String id = invocation.getArgument(0).toString();
return of(new RepositoryMigrationPlan.RepositoryMigrationEntry(id, MOVE, "namespace-" + id, "name-" + id));
};
lenient().when(migrationStrategyDao.get("3b91caa5-59c3-448f-920b-769aaa56b761")).thenAnswer(planAnswer);
lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenAnswer(planAnswer);
lenient().when(migrationStrategyDao.get("454972da-faf9-4437-b682-dc4a4e0aa8eb")).thenAnswer(planAnswer);
}
@Test
@@ -104,56 +109,20 @@ class XmlRepositoryV1UpdateStepTest {
void shouldMapAttributes() throws JAXBException {
updateStep.doUpdate();
Optional<Repository> repository = findByNamespace("git");
Optional<Repository> repository = findByNamespace("namespace-3b91caa5-59c3-448f-920b-769aaa56b761");
assertThat(repository)
.get()
.hasFieldOrPropertyWithValue("type", "git")
.hasFieldOrPropertyWithValue("contact", "arthur@dent.uk")
.hasFieldOrPropertyWithValue("description", "A simple repository without directories.");
}
@Test
void shouldUseRepositoryTypeAsNamespaceForNamesWithSingleElement() throws JAXBException {
updateStep.doUpdate();
Optional<Repository> repository = findByNamespace("git");
assertThat(repository)
.get()
.hasFieldOrPropertyWithValue("namespace", "git")
.hasFieldOrPropertyWithValue("name", "simple");
}
@Test
void shouldUseDirectoriesForNamespaceAndNameForNamesWithTwoElements() throws JAXBException {
updateStep.doUpdate();
Optional<Repository> repository = findByNamespace("one");
assertThat(repository)
.get()
.hasFieldOrPropertyWithValue("namespace", "one")
.hasFieldOrPropertyWithValue("name", "directory");
}
@Test
void shouldUseDirectoriesForNamespaceAndConcatenatedNameForNamesWithMoreThanTwoElements() throws JAXBException {
updateStep.doUpdate();
Optional<Repository> repository = findByNamespace("some");
assertThat(repository)
.get()
.hasFieldOrPropertyWithValue("namespace", "some")
.hasFieldOrPropertyWithValue("name", "more_directories_than_one");
.hasFieldOrPropertyWithValue("description", "A repository with two folders.");
}
@Test
void shouldMapPermissions() throws JAXBException {
updateStep.doUpdate();
Optional<Repository> repository = findByNamespace("git");
Optional<Repository> repository = findByNamespace("namespace-454972da-faf9-4437-b682-dc4a4e0aa8eb");
assertThat(repository.get().getPermissions())
.hasSize(3)
@@ -176,14 +145,27 @@ class XmlRepositoryV1UpdateStepTest {
@Test
void shouldUseDirectoryFromStrategy(@TempDirectory.TempDir Path tempDir) throws JAXBException {
Path targetDir = tempDir.resolve("someDir");
MigrationStrategy.Instance strategyMock = injectorMock.getInstance(InlineMigrationStrategy.class);
when(strategyMock.migrate("454972da-faf9-4437-b682-dc4a4e0aa8eb", "simple", "git")).thenReturn(targetDir);
MigrationStrategy.Instance strategyMock = injectorMock.getInstance(MoveMigrationStrategy.class);
when(strategyMock.migrate("454972da-faf9-4437-b682-dc4a4e0aa8eb", "simple", "git")).thenReturn(of(targetDir));
updateStep.doUpdate();
assertThat(locationCaptor.getAllValues()).contains(targetDir);
}
@Test
void shouldSkipWhenStrategyGivesNoNewPath() throws JAXBException {
for (MigrationStrategy strategy : MigrationStrategy.values()) {
MigrationStrategy.Instance strategyMock = mock(strategy.getImplementationClass());
lenient().when(strategyMock.migrate(any(), any(), any())).thenReturn(empty());
lenient().when(injectorMock.getInstance((Class<MigrationStrategy.Instance>) strategy.getImplementationClass())).thenReturn(strategyMock);
}
updateStep.doUpdate();
assertThat(locationCaptor.getAllValues()).isEmpty();
}
@Test
void shouldFailForMissingMigrationStrategy() {
lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenReturn(empty());
@@ -221,6 +203,25 @@ class XmlRepositoryV1UpdateStepTest {
assertThat(tempDir.resolve("config").resolve("repositories.xml.v1.backup")).doesNotExist();
}
@Test
void shouldGetNoMissingStrategiesWithFormerV2DatabaseFile(@TempDirectory.TempDir Path tempDir) throws IOException {
createFormerV2RepositoriesFile(tempDir);
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies()).isEmpty();
}
@Test
void shouldFindMissingStrategies(@TempDirectory.TempDir Path tempDir) throws IOException {
V1RepositoryFileSystem.createV1Home(tempDir);
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies())
.extracting("id")
.contains(
"3b91caa5-59c3-448f-920b-769aaa56b761",
"c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f",
"454972da-faf9-4437-b682-dc4a4e0aa8eb");
}
private void createFormerV2RepositoriesFile(@TempDirectory.TempDir Path tempDir) throws IOException {
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
Path configDir = tempDir.resolve("config");