CLI Support for repository actions (#1987)

To make SCM-Manager more accessible and to make it easier using scripts against the server, we created a command line interface. This command line interface can be used to perform the default actions like create, modify and delete repositories. It is also very flexible and can be extended by plugins.

The CLI already supports internationalization, help texts, input validation, loose and table-like templates and nested subcommands. Check the cli guidelines to learn how add new cli commands.

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2022-04-04 12:02:16 +02:00
committed by GitHub
parent 07afe4b439
commit 162dd6ad0a
90 changed files with 5303 additions and 21 deletions

View File

@@ -0,0 +1,171 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cli;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import picocli.CommandLine;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CliProcessorTest {
@Mock
private CommandRegistry registry;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CliContext context;
@BeforeEach
void setDefaultLocale() {
when(context.getLocale()).thenReturn(Locale.ENGLISH);
}
@Test
void shouldExecutePingCommand() {
when(registry.createCommandTree()).thenReturn(Collections.singleton(new RegisteredCommandNode("ping", PingCommand.class)));
Injector injector = Guice.createInjector();
CliProcessor cliProcessor = new CliProcessor(registry, injector);
cliProcessor.execute(context, "ping");
verify(context.getStdout()).println("PONG");
}
@Test
void shouldExecutePingCommandWithExitCode0() {
when(registry.createCommandTree()).thenReturn(Collections.singleton(new RegisteredCommandNode("ping", PingCommand.class)));
Injector injector = Guice.createInjector();
CliProcessor cliProcessor = new CliProcessor(registry, injector);
int exitCode = cliProcessor.execute(context, "ping");
assertThat(exitCode).isZero();
}
@Test
void shouldPrintCommandOne() {
String result = executeHierachyCommands("--help");
assertThat(result).contains("Commands:\n" +
" one");
}
@Test
void shouldPrintCommandTwo() {
String result = executeHierachyCommands("one","--help");
assertThat(result).contains("Commands:\n" +
" two");
}
@Test
void shouldPrintCommandThree() {
String result = executeHierachyCommands("one", "two","--help");
assertThat(result).contains("Commands:\n" +
" three");
}
@Nonnull
private String executeHierachyCommands(String... args) {
RegisteredCommandNode one = new RegisteredCommandNode("one", RootCommand.class);
RegisteredCommandNode two = new RegisteredCommandNode("two", SubCommand.class);
RegisteredCommandNode three = new RegisteredCommandNode("three", SubSubCommand.class);
two.getChildren().add(three);
one.getChildren().add(two);
when(registry.createCommandTree()).thenReturn(Collections.singleton(one));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
when(context.getStdout()).thenReturn(new PrintWriter(baos));
Injector injector = Guice.createInjector();
CliProcessor cliProcessor = new CliProcessor(registry, injector);
cliProcessor.execute(context, args);
return baos.toString();
}
@Test
void shouldUseResourceBundleFromAnnotationWithContextLocale() {
when(context.getLocale()).thenReturn(Locale.GERMAN);
String helpForThree = executeHierachyCommands("one", "two", "three", "--help");
assertThat(helpForThree).contains("Dies ist meine App.");
}
@Test
void shouldUseDefaultWithoutResourceBundle() {
when(context.getLocale()).thenReturn(Locale.GERMAN);
String helpForTwo = executeHierachyCommands("one", "two", "--help");
assertThat(helpForTwo).contains("Dies ist meine App.");
}
@CommandLine.Command(name = "one")
static class RootCommand implements Runnable {
@Override
public void run() {
}
}
@CommandLine.Command(name = "two")
static class SubCommand implements Runnable {
@Override
public void run() {
}
}
@CommandLine.Command(name = "three", resourceBundle = "sonia.scm.cli.test")
static class SubSubCommand implements Runnable {
@Override
public void run() {
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cli;
import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CommandRegistryTest {
@Mock
private RegisteredCommandCollector commandCollector;
@InjectMocks
private CommandRegistry registry;
@Test
void shouldCreateTreeWithOnlyRootNodes() {
mockCommands(rc(Object.class), rc(String.class), rc(Integer.class));
Set<RegisteredCommandNode> commandTree = registry.createCommandTree();
assertContainsCommands(commandTree, Object.class, String.class, Integer.class);
}
@Test
void shouldCreateTreeWithParents() {
mockCommands(rc(Object.class), rc(String.class, Object.class), rc(Integer.class, Object.class));
Set<RegisteredCommandNode> commandTree = registry.createCommandTree();
assertContainsCommands(commandTree, Object.class);
assertContainsCommands(commandTree.iterator().next().getChildren(), Integer.class, String.class);
}
@Test
void shouldCreateTreeWithParentsSecondLevel() {
mockCommands(rc(Object.class), rc(String.class, Object.class), rc(Integer.class, String.class));
Set<RegisteredCommandNode> commandTree = registry.createCommandTree();
assertContainsCommands(commandTree, Object.class);
RegisteredCommandNode rootNode = commandTree.iterator().next();
assertContainsCommands(rootNode.getChildren(), String.class);
assertContainsCommands(rootNode.getChildren().get(0).getChildren(), Integer.class);
}
private void mockCommands(RegisteredCommand... commands) {
when(commandCollector.collect()).thenReturn(ImmutableSet.copyOf(commands));
}
private RegisteredCommand rc(Class<?> command) {
return rc(command, null);
}
private RegisteredCommand rc(Class<?> command, Class<?> parent) {
return new RegisteredCommand(command.getSimpleName(), command, parent);
}
private void assertContainsCommands(Collection<RegisteredCommandNode> nodes, Class... expected) {
assertThat(nodes).map(RegisteredCommandNode::getCommand).contains(expected);
}
}

View File

@@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
class JsonStreamingCliContextTest {
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void shouldPrintJsonOnStdout() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (JsonStreamingCliContext jsonStreamingCliContext = new JsonStreamingCliContext(Locale.ENGLISH, bais, baos)) {
jsonStreamingCliContext.getStdout().print("Hello");
}
JsonNode json = mapper.readTree(new ByteArrayInputStream(baos.toByteArray()));
assertThat(json.isArray()).isTrue();
assertThat(json.get(0).get("out").asText()).isEqualTo("Hello");
}
@Test
void shouldPrintJsonOnStdoutAndStderr() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (JsonStreamingCliContext jsonStreamingCliContext = new JsonStreamingCliContext(Locale.ENGLISH, bais, baos)) {
jsonStreamingCliContext.getStdout().print("Hello");
jsonStreamingCliContext.getStderr().print("Error 1: Failed");
jsonStreamingCliContext.getStdout().print(" World");
}
JsonNode json = mapper.readTree(new ByteArrayInputStream(baos.toByteArray()));
assertThat(json.isArray()).isTrue();
assertThat(json.get(0).get("out").asText()).isEqualTo("Hello");
assertThat(json.get(1).get("err").asText()).isEqualTo("Error 1: Failed");
assertThat(json.get(2).get("out").asText()).isEqualTo(" World");
}
@Test
void shouldReturnExitCode() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (JsonStreamingCliContext jsonStreamingCliContext = new JsonStreamingCliContext(Locale.ENGLISH, bais, baos)) {
jsonStreamingCliContext.getStdout().print("Hello");
jsonStreamingCliContext.writeExit(1);
}
JsonNode json = mapper.readTree(new ByteArrayInputStream(baos.toByteArray()));
assertThat(json.isArray()).isTrue();
assertThat(json.get(0).get("out").asText()).isEqualTo("Hello");
assertThat(json.get(1).get("exit").asInt()).isEqualTo(1);
}
}

View File

@@ -0,0 +1,93 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cli;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.NamedClassElement;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.ScmModule;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RegisteredCommandCollectorTest {
@Mock
private PluginLoader pluginLoader;
@InjectMocks
private RegisteredCommandCollector commandCollector;
@Test
void shouldCollectCommandsFromModulesAndPlugins() {
ScmModule module = mock(ScmModule.class);
when(pluginLoader.getInstalledModules()).thenReturn(ImmutableList.of(module));
when(module.getCliCommands()).thenReturn(ImmutableList.of(new NamedClassElement("moduleCommand", ModuleCommand.class.getName())));
InstalledPlugin installedPlugin = mock(InstalledPlugin.class);
InstalledPluginDescriptor descriptor = mock(InstalledPluginDescriptor.class);
when(pluginLoader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedPlugin));
when(installedPlugin.getDescriptor()).thenReturn(descriptor);
when(descriptor.getCliCommands()).thenReturn(ImmutableList.of(new NamedClassElement("subCommand", SubCommand.class.getName())));
when(pluginLoader.getUberClassLoader()).thenReturn(RegisteredCommandCollectorTest.class.getClassLoader());
Set<RegisteredCommand> commands = commandCollector.collect();
assertThat(commands).hasSize(2);
assertThat(commands)
.map(RegisteredCommand::getName)
.containsExactlyInAnyOrder("subCommand", "moduleCommand");
List<Class<?>> commandClasses = commands.stream().map(RegisteredCommand::getCommand).collect(Collectors.toList());
assertThat(commandClasses).containsExactlyInAnyOrder(SubCommand.class, ModuleCommand.class);
}
static class ParentCommand {
}
@sonia.scm.cli.ParentCommand(value = ParentCommand.class)
static class SubCommand {
}
@sonia.scm.cli.ParentCommand(value = ParentCommand.class)
static class ModuleCommand {
}
}

View File

@@ -0,0 +1,108 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.cli.CommandValidator;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryCreateCommandTest {
@Mock
private RepositoryManager manager;
@Mock
private RepositoryInitializer initializer;
@Mock
private RepositoryTemplateRenderer templateRenderer;
@Mock
private CommandValidator commandValidator;
@InjectMocks
private RepositoryCreateCommand command;
@Test
void shouldValidate() {
command.setType("git");
command.setRepository("test/repo");
command.run();
verify(commandValidator).validate();
}
@Test
void shouldCreateRepoWithoutInit() {
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
command.setType(heartOfGold.getType());
command.setRepository(heartOfGold.getNamespaceAndName().toString());
command.run();
verify(manager).create(argThat(repository -> {
assertThat(repository.getType()).isEqualTo(heartOfGold.getType());
return true;
}));
verify(initializer, never()).initialize(eq(heartOfGold), anyMap());
}
@Test
void shouldCreateRepoWithInit() {
Repository puzzle = RepositoryTestData.create42Puzzle();
when(manager.create(any())).thenReturn(puzzle);
command.setType(puzzle.getType());
command.setRepository(puzzle.getNamespaceAndName().toString());
command.setInit(true);
command.run();
verify(initializer).initialize(eq(puzzle), anyMap());
}
@Test
void shouldRenderTemplateAfterCreation() {
Repository puzzle = RepositoryTestData.create42Puzzle();
when(manager.create(any())).thenReturn(puzzle);
command.setType(puzzle.getType());
command.setRepository(puzzle.getNamespaceAndName().toString());
command.run();
verify(templateRenderer).render(puzzle);
}
}

View File

@@ -0,0 +1,81 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.cli.CliContext;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryDeleteCommandTest {
@Mock
private RepositoryTemplateRenderer templateRenderer;
@Mock
private RepositoryManager manager;
@InjectMocks
private RepositoryDeleteCommand command;
@Test
void shouldRenderPromptWithoutYesFlag() {
command.setRepository("test/repo");
command.run();
verify(templateRenderer).renderToStderr(any(), anyMap());
}
@Test
void shouldExitOnInvalidInput() {
command.setRepository("test");
command.setShouldDelete(true);
command.run();
verify(templateRenderer).renderInvalidInputError();
}
@Test
void shouldDeleteRepository() {
Repository puzzle = RepositoryTestData.create42Puzzle();
when(manager.get(new NamespaceAndName("test", "r"))).thenReturn(puzzle);
command.setRepository("test/r");
command.setShouldDelete(true);
command.run();
verify(manager).delete(puzzle);
}
}

View File

@@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.cli.RepositoryGetCommand;
import sonia.scm.repository.cli.RepositoryTemplateRenderer;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryGetCommandTest {
@Mock
private RepositoryManager repositoryManager;
@Mock
private RepositoryTemplateRenderer templateRenderer;
@InjectMocks
private RepositoryGetCommand command;
@Test
void shouldRenderNotFoundError() {
String repo = "test/repo";
when(repositoryManager.get(new NamespaceAndName("test", "repo"))).thenReturn(null);
command.setRepository(repo);
command.run();
verify(templateRenderer).renderNotFoundError();
}
@Test
void shouldRenderInvalidInputError() {
String repo = "repo";
command.setRepository(repo);
command.run();
verify(templateRenderer).renderInvalidInputError();
}
@Test
void shouldRenderTemplateToStdout() {
String repo = "test/repo";
Repository puzzle = RepositoryTestData.create42Puzzle();
when(repositoryManager.get(new NamespaceAndName("test", "repo"))).thenReturn(puzzle);
command.setRepository(repo);
command.run();
verify(templateRenderer).render(puzzle);
}
}

View File

@@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.cli.Table;
import sonia.scm.cli.TemplateRenderer;
import sonia.scm.repository.RepositoryManager;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryListCommandTest {
@Mock
private TemplateRenderer templateRenderer;
@Mock
private RepositoryManager manager;
@Mock
private RepositoryToRepositoryCommandDtoMapper mapper;
@InjectMocks
private RepositoryListCommand command;
@Test
void shouldReturnShortTemplate() {
command.setShortTemplate(true);
command.run();
verify(templateRenderer, never()).createTable();
verify(templateRenderer).renderToStdout(any(), anyMap());
}
@Test
void shouldReturnTableTemplate() {
Table table = mock(Table.class);
when(templateRenderer.createTable()).thenReturn(table);
ArgumentCaptor<Map<String, Object>> mapCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<String> templateCaptor = ArgumentCaptor.forClass(String.class);
doNothing().when(templateRenderer).renderToStdout(templateCaptor.capture(), mapCaptor.capture());
command.run();
Map<String, Object> map = mapCaptor.getValue();
assertThat(map).hasSize(2);
assertThat(map.get("rows")).isInstanceOf(Table.class);
assertThat(map.get("repos")).isInstanceOf(List.class);
}
}

View File

@@ -0,0 +1,90 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.cli.CommandValidator;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryModifyCommandTest {
@Mock
private RepositoryTemplateRenderer templateRenderer;
@Mock
private CommandValidator validator;
@Mock
private RepositoryManager manager;
@InjectMocks
private RepositoryModifyCommand command;
@Test
void shouldValidateParameters() {
command.setRepository("test/repo");
command.run();
verify(validator).validate();
}
@Test
void shouldRenderInvalidInputError() {
command.setRepository("test");
command.run();
verify(templateRenderer).renderInvalidInputError();
}
@Test
void shouldRenderNotFoundError() {
when(manager.get(new NamespaceAndName("test", "repo"))).thenReturn(null);
command.setRepository("test/repo");
command.run();
verify(templateRenderer).renderNotFoundError();
}
@Test
void shouldModifyRepository() {
Repository puzzle = RepositoryTestData.create42Puzzle();
when(manager.get(new NamespaceAndName("test", "repo"))).thenReturn(puzzle);
command.setRepository("test/repo");
command.run();
verify(manager).modify(puzzle);
verify(templateRenderer).render(puzzle);
}
}

View File

@@ -0,0 +1,95 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.cli;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryToRepositoryCommandDtoMapperTest {
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService service;
private RepositoryToRepositoryCommandDtoMapperImpl mapper;
@BeforeEach
void initMapper() {
mapper = new RepositoryToRepositoryCommandDtoMapperImpl();
mapper.setServiceFactory(serviceFactory);
}
@Test
void shouldMapAttributes() {
Repository testRepo = RepositoryTestData.create42Puzzle();
when(serviceFactory.create(testRepo)).thenReturn(service);
RepositoryCommandDto dto = mapper.map(testRepo);
assertThat(dto.getNamespace()).isEqualTo(testRepo.getNamespace());
assertThat(dto.getName()).isEqualTo(testRepo.getName());
assertThat(dto.getContact()).isEqualTo(testRepo.getContact());
assertThat(dto.getDescription()).isEqualTo(testRepo.getDescription());
}
@Test
void shouldAppendHttpUrl() {
ScmProtocol scmProtocol = new ScmProtocol() {
@Override
public String getType() {
return "http";
}
@Override
public String getUrl() {
return "http://localhost:8081/scm";
}
};
Repository testRepo = RepositoryTestData.create42Puzzle();
RepositoryService service = mock(RepositoryService.class);
when(serviceFactory.create(testRepo)).thenReturn(service);
when(service.getSupportedProtocols()).thenReturn(ImmutableList.of(scmProtocol).stream());
RepositoryCommandDto dto = mapper.map(testRepo);
assertThat(dto.getUrl()).isEqualTo("http://localhost:8081/scm");
}
}

View File

@@ -0,0 +1,78 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.Validator;
import javax.validation.constraints.Size;
import static org.assertj.core.api.Assertions.assertThat;
class DefaultValidatorProviderTest {
@Test
void shouldCreateValidatorWithConstraintValidatorFactory() {
TestingConstraintValidatorFactory constraintValidatorFactory = new TestingConstraintValidatorFactory();
DefaultValidatorProvider provider = new DefaultValidatorProvider(constraintValidatorFactory);
Validator validator = provider.get();
validator.validate(new Sample("one"));
assertThat(constraintValidatorFactory.counter).isOne();
}
private static class TestingConstraintValidatorFactory implements ConstraintValidatorFactory {
private int counter = 0;
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
counter++;
try {
return key.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
}
}
private static class Sample {
@Size(max = 20)
private final String value;
public Sample(String value) {
this.value = value;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import com.google.inject.Guice;
import com.google.inject.Injector;
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.repository.RepositoryManager;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class GuiceConstraintValidatorFactoryTest {
@Mock
private RepositoryManager repositoryManager;
@Test
void shouldUseInjectorToCreateConstraintInstance() {
Injector injector = Guice.createInjector(new RepositoryManagerModule(repositoryManager));
GuiceConstraintValidatorFactory factory = new GuiceConstraintValidatorFactory(injector);
RepositoryTypeConstraintValidator instance = factory.getInstance(RepositoryTypeConstraintValidator.class);
assertThat(instance.getRepositoryManager()).isSameAs(repositoryManager);
}
}

View File

@@ -0,0 +1,42 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import com.google.inject.AbstractModule;
import sonia.scm.repository.RepositoryManager;
public class RepositoryManagerModule extends AbstractModule {
private final RepositoryManager repositoryManager;
public RepositoryManagerModule(RepositoryManager repositoryManager) {
this.repositoryManager = repositoryManager;
}
@Override
protected void configure() {
bind(RepositoryManager.class).toInstance(repositoryManager);
}
}

View File

@@ -0,0 +1,43 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RepositoryTypeConstraintValidator.class)
public @interface RepositoryType {
String message() default "Invalid type";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}

View File

@@ -0,0 +1,51 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import sonia.scm.repository.RepositoryManager;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class RepositoryTypeConstraintValidator implements ConstraintValidator<RepositoryType, String> {
private final RepositoryManager repositoryManager;
@Inject
public RepositoryTypeConstraintValidator(RepositoryManager repositoryManager) {
this.repositoryManager = repositoryManager;
}
public RepositoryManager getRepositoryManager() {
return repositoryManager;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return repositoryManager.getConfiguredTypes()
.stream().anyMatch(t -> t.getName().equalsIgnoreCase(value));
}
}

View File

@@ -0,0 +1,132 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.validation;
import com.google.common.collect.ImmutableList;
import com.google.inject.Guice;
import com.google.inject.Injector;
import lombok.AllArgsConstructor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
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.repository.RepositoryManager;
import javax.inject.Inject;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Collections;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
class ValidationModuleTest {
@Nested
class SimpleInjectionTests {
private Injector injector;
@BeforeEach
void setUpInjector() {
injector = Guice.createInjector(new ValidationModule());
}
@Test
void shouldInjectValidator() {
WithValidator instance = injector.getInstance(WithValidator.class);
assertThat(instance.validator).isNotNull();
}
@Test
void shouldInjectConstraintValidatorFactory() {
WithConstraintValidatorFactory instance = injector.getInstance(WithConstraintValidatorFactory.class);
assertThat(instance.constraintValidatorFactory).isInstanceOf(GuiceConstraintValidatorFactory.class);
}
}
@Nested
@ExtendWith(MockitoExtension.class)
class RealWorldTests {
@Mock
private RepositoryManager repositoryManager;
@Test
void shouldValidateRepositoryTypes() {
when(repositoryManager.getConfiguredTypes()).thenReturn(ImmutableList.of(
new sonia.scm.repository.RepositoryType("git", "Git", Collections.emptySet()),
new sonia.scm.repository.RepositoryType("hg", "Mercurial", Collections.emptySet())
));
Injector injector = Guice.createInjector(new ValidationModule(), new RepositoryManagerModule(repositoryManager));
Validator validator = injector.getInstance(Validator.class);
Set<ConstraintViolation<Repository>> violations = validator.validate(new Repository("svn"));
assertThat(violations).isNotEmpty();
violations = validator.validate(new Repository("git"));
assertThat(violations).isEmpty();
}
}
public static class WithValidator {
private final Validator validator;
@Inject
public WithValidator(Validator validator) {
this.validator = validator;
}
}
public static class WithConstraintValidatorFactory {
private final ConstraintValidatorFactory constraintValidatorFactory;
@Inject
public WithConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
this.constraintValidatorFactory = constraintValidatorFactory;
}
}
@AllArgsConstructor
public static class Repository {
@RepositoryType
private String type;
}
}