From d2e81ce121cb0f8f9fba44bec487c35b06ee17cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 11 Apr 2022 10:04:19 +0200 Subject: [PATCH] Add cli commands for users and groups (#1993) Adds cli commands to manage users and groups. Co-authored-by: Matthias Thieroff --- gradle/changelog/cli.yaml | 2 +- .../java/sonia/scm/cli/TemplateRenderer.java | 12 +- .../scm/repository/cli/GroupCommand.java | 30 +++ .../java/sonia/scm/user/cli/UserCommand.java | 30 +++ .../sonia/scm/cli/CommandValidatorTest.java | 16 +- .../main/java/sonia/scm/cli/CliProcessor.java | 14 +- .../scm/group/cli/GroupAddMemberCommand.java | 79 ++++++ .../sonia/scm/group/cli/GroupCommandBean.java | 41 +++ .../scm/group/cli/GroupCommandBeanMapper.java | 55 ++++ .../scm/group/cli/GroupCreateCommand.java | 87 +++++++ .../scm/group/cli/GroupDeleteCommand.java | 80 ++++++ .../sonia/scm/group/cli/GroupGetCommand.java | 67 +++++ .../sonia/scm/group/cli/GroupListCommand.java | 100 ++++++++ .../scm/group/cli/GroupModifyCommand.java | 108 ++++++++ .../group/cli/GroupRemoveMemberCommand.java | 79 ++++++ .../scm/group/cli/GroupTemplateRenderer.java | 75 ++++++ .../repository/cli/RepositoryGetCommand.java | 2 +- .../scm/user/cli/UserActivateCommand.java | 67 +++++ .../sonia/scm/user/cli/UserCommandBean.java | 44 ++++ .../scm/user/cli/UserCommandBeanMapper.java | 45 ++++ .../cli/UserConvertToExternalCommand.java | 63 +++++ .../cli/UserConvertToInternalCommand.java | 74 ++++++ .../sonia/scm/user/cli/UserCreateCommand.java | 133 ++++++++++ .../scm/user/cli/UserDeactivateCommand.java | 67 +++++ .../sonia/scm/user/cli/UserDeleteCommand.java | 77 ++++++ .../sonia/scm/user/cli/UserGetCommand.java | 60 +++++ .../sonia/scm/user/cli/UserListCommand.java | 99 ++++++++ .../sonia/scm/user/cli/UserModifyCommand.java | 107 ++++++++ .../scm/user/cli/UserTemplateRenderer.java | 95 +++++++ .../resources/sonia/scm/cli/i18n.properties | 108 +++++++- .../sonia/scm/cli/i18n_de.properties | 106 +++++++- .../sonia/scm/cli/TemplateTestRenderer.java | 117 +++++++++ .../group/cli/GroupAddMemberCommandTest.java | 122 +++++++++ .../scm/group/cli/GroupCreateCommandTest.java | 103 ++++++++ .../scm/group/cli/GroupDeleteCommandTest.java | 98 ++++++++ .../scm/group/cli/GroupGetCommandTest.java | 95 +++++++ .../scm/group/cli/GroupListCommandTest.java | 85 +++++++ .../scm/group/cli/GroupModifyCommandTest.java | 180 +++++++++++++ .../cli/GroupRemoveMemberCommandTest.java | 122 +++++++++ .../group/cli/GroupTemplateTestRenderer.java | 52 ++++ .../template/MustacheTemplateTestEngine.java | 54 ++++ .../scm/user/cli/UserActivateCommandTest.java | 188 ++++++++++++++ .../cli/UserConvertToExternalCommandTest.java | 155 ++++++++++++ .../cli/UserConvertToInternalCommandTest.java | 157 ++++++++++++ .../scm/user/cli/UserCreateCommandTest.java | 237 ++++++++++++++++++ .../user/cli/UserDeactivateCommandTest.java | 188 ++++++++++++++ .../scm/user/cli/UserDeleteCommandTest.java | 132 ++++++++++ .../scm/user/cli/UserGetCommandTest.java | 136 ++++++++++ .../scm/user/cli/UserListCommandTest.java | 120 +++++++++ .../scm/user/cli/UserModifyCommandTest.java | 233 +++++++++++++++++ .../user/cli/UserTemplateTestRenderer.java | 56 +++++ 51 files changed, 4640 insertions(+), 12 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/cli/GroupCommand.java create mode 100644 scm-core/src/main/java/sonia/scm/user/cli/UserCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupAddMemberCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBean.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBeanMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupCreateCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupDeleteCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupGetCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupListCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupModifyCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupRemoveMemberCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserActivateCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBean.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBeanMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToExternalCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToInternalCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserCreateCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserDeactivateCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserDeleteCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserGetCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserListCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserModifyCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java create mode 100644 scm-webapp/src/test/java/sonia/scm/cli/TemplateTestRenderer.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupCreateCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupDeleteCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupListCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupTemplateTestRenderer.java create mode 100644 scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTestEngine.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserActivateCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToExternalCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToInternalCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserCreateCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserDeactivateCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserDeleteCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserGetCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserListCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserModifyCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserTemplateTestRenderer.java diff --git a/gradle/changelog/cli.yaml b/gradle/changelog/cli.yaml index 86a21aa41d..8766950241 100644 --- a/gradle/changelog/cli.yaml +++ b/gradle/changelog/cli.yaml @@ -1,2 +1,2 @@ - type: added - description: Add cli support with repository actions ([#1987](https://github.com/scm-manager/scm-manager/pull/1987)) + description: Add cli support for repositories, users and groups ([#1987](https://github.com/scm-manager/scm-manager/pull/1987), [#1993](https://github.com/scm-manager/scm-manager/pull/1993)) diff --git a/scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java b/scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java index fea8f7a140..c62591a7ad 100644 --- a/scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java +++ b/scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java @@ -111,7 +111,15 @@ public class TemplateRenderer { * @return table for templating content */ public Table createTable() { - return new Table(getSpecOrDie().resourceBundle()); + return new Table(getBundle()); + } + + protected CliContext getContext() { + return context; + } + + protected ResourceBundle getBundle() { + return getSpecOrDie().resourceBundle(); } private CommandLine.Model.CommandSpec getSpecOrDie() { @@ -137,7 +145,7 @@ public class TemplateRenderer { UnaryOperator upper = value -> value.toUpperCase(context.getLocale()); finalModel.put("upper", upper); - ResourceBundle resourceBundle = getSpecOrDie().resourceBundle(); + ResourceBundle resourceBundle = getBundle(); if (resourceBundle != null) { finalModel.put("i18n", new I18n(resourceBundle)); } diff --git a/scm-core/src/main/java/sonia/scm/repository/cli/GroupCommand.java b/scm-core/src/main/java/sonia/scm/repository/cli/GroupCommand.java new file mode 100644 index 0000000000..300a8cf00a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/cli/GroupCommand.java @@ -0,0 +1,30 @@ +/* + * 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 picocli.CommandLine; + +@CommandLine.Command(name = "group") +public class GroupCommand {} diff --git a/scm-core/src/main/java/sonia/scm/user/cli/UserCommand.java b/scm-core/src/main/java/sonia/scm/user/cli/UserCommand.java new file mode 100644 index 0000000000..c37ede77c9 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/user/cli/UserCommand.java @@ -0,0 +1,30 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; + +@CommandLine.Command(name = "user") +public class UserCommand {} diff --git a/scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java b/scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java index f71d4fe473..4927c93a5d 100644 --- a/scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java +++ b/scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java @@ -49,7 +49,12 @@ class CommandValidatorTest { @Test void shouldValidateCommand() { - ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.ENGLISH); + ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.ENGLISH, new ResourceBundle.Control() { + @Override + public Locale getFallbackLocale(String baseName, Locale locale) { + return Locale.ROOT; + } + }); when(context.getLocale()).thenReturn(Locale.ENGLISH); CommandLine commandLine = new CommandLine(Command.class, new TestingCommandFactory()); commandLine.setResourceBundle(resourceBundle); @@ -63,7 +68,12 @@ class CommandValidatorTest { @Test void shouldValidateCommandWithGermanLocale() { - ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.GERMAN); + ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.GERMAN, new ResourceBundle.Control() { + @Override + public Locale getFallbackLocale(String baseName, Locale locale) { + return Locale.ROOT; + } + }); when(context.getLocale()).thenReturn(Locale.GERMAN); CommandLine commandLine = new CommandLine(Command.class, new TestingCommandFactory()); commandLine.setResourceBundle(resourceBundle); @@ -79,7 +89,7 @@ class CommandValidatorTest { public static class Command implements Runnable { @CommandLine.Mixin - private CommandValidator commandValidator; + private final CommandValidator commandValidator; @Email @CommandLine.Option(names = "--mail") diff --git a/scm-webapp/src/main/java/sonia/scm/cli/CliProcessor.java b/scm-webapp/src/main/java/sonia/scm/cli/CliProcessor.java index b379c06364..2e512ab766 100644 --- a/scm-webapp/src/main/java/sonia/scm/cli/CliProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/cli/CliProcessor.java @@ -29,6 +29,7 @@ import picocli.AutoComplete; import picocli.CommandLine; import javax.inject.Inject; +import java.util.Locale; import java.util.ResourceBundle; public class CliProcessor { @@ -50,7 +51,7 @@ public class CliProcessor { CommandFactory factory = new CommandFactory(injector, context); CommandLine cli = new CommandLine(ScmManagerCommand.class, factory); cli.getCommandSpec().addMixin("help", usageHelp); - cli.setResourceBundle(ResourceBundle.getBundle("sonia.scm.cli.i18n", context.getLocale())); + cli.setResourceBundle(getBundle("sonia.scm.cli.i18n", context.getLocale())); for (RegisteredCommandNode c : registry.createCommandTree()) { CommandLine commandline = createCommandline(context, factory, c); cli.getCommandSpec().addSubcommand(c.getName(), commandline); @@ -69,7 +70,7 @@ public class CliProcessor { ResourceBundle resourceBundle = commandLine.getCommandSpec().resourceBundle(); if (resourceBundle != null) { String resourceBundleBaseName = resourceBundle.getBaseBundleName(); - commandLine.setResourceBundle(ResourceBundle.getBundle(resourceBundleBaseName, context.getLocale())); + commandLine.setResourceBundle(getBundle(resourceBundleBaseName, context.getLocale())); } for (RegisteredCommandNode child : command.getChildren()) { if (!commandLine.getCommandSpec().subcommands().containsKey(child.getName())) { @@ -80,4 +81,13 @@ public class CliProcessor { return commandLine; } + + private ResourceBundle getBundle(String baseName, Locale locale) { + return ResourceBundle.getBundle(baseName, locale, new ResourceBundle.Control() { + @Override + public Locale getFallbackLocale(String baseName, Locale locale) { + return Locale.ROOT; + } + }); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupAddMemberCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupAddMemberCommand.java new file mode 100644 index 0000000000..61f44f3953 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupAddMemberCommand.java @@ -0,0 +1,79 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; +import java.util.Arrays; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "add-member", aliases = "add") +class GroupAddMemberCommand implements Runnable { + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @CommandLine.Parameters(index = "0", arity = "1", descriptionKey = "scm.group.add-member.name") + private String name; + @CommandLine.Parameters(index = "1..", arity = "1..", descriptionKey = "scm.group.add-member.members") + private String[] members; + + @Inject + GroupAddMemberCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + Group existingGroup = manager.get(name); + if (existingGroup == null) { + templateRenderer.renderNotFoundError(); + } else { + Arrays.stream(members).forEach(existingGroup::add); + manager.modify(existingGroup); + Group modifiedGroup = manager.get(name); + templateRenderer.render(modifiedGroup); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setMembers(String[] members) { + this.members = members; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBean.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBean.java new file mode 100644 index 0000000000..42ba6187a2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBean.java @@ -0,0 +1,41 @@ +/* + * 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.group.cli; + +import lombok.Data; + +import java.util.List; + +@Data +class GroupCommandBean { + + private String name; + private String description; + private List members; + private String membersList; + private boolean external; + private String creationDate; + private String lastModified; +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBeanMapper.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBeanMapper.java new file mode 100644 index 0000000000..d870a24d9a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCommandBeanMapper.java @@ -0,0 +1,55 @@ +/* + * 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.group.cli; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.group.Group; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +@Mapper +interface GroupCommandBeanMapper { + + @Mapping(target = "membersList", ignore = true) + GroupCommandBean map(Group group); + + @AfterMapping + default void mapMembersList(@MappingTarget GroupCommandBean bean) { + if (bean.getMembers() != null) { + bean.setMembersList(String.join(", ", bean.getMembers())); + } + } + + default String mapTimestampToISODate(Long timestamp) { + if (timestamp != null) { + return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(timestamp)); + } + return null; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCreateCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCreateCommand.java new file mode 100644 index 0000000000..436877e138 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupCreateCommand.java @@ -0,0 +1,87 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "create") +class GroupCreateCommand implements Runnable { + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @CommandLine.Parameters(descriptionKey = "scm.group.create.name") + private String name; + + @CommandLine.Option(names = {"--description", "-d"}, descriptionKey = "scm.group.create.desc") + private String description; + + @CommandLine.Option(names = {"--member", "-m"}, descriptionKey = "scm.group.create.member") + private String[] members; + + @CommandLine.Option(names = {"--external", "-e"}, descriptionKey = "scm.group.create.external") + private boolean external; + + @Inject + GroupCreateCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + Group newGroup = new Group("xml", name, members); + newGroup.setDescription(description); + newGroup.setExternal(external); + Group createdGroup = manager.create(newGroup); + templateRenderer.render(createdGroup); + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setDescription(String description) { + this.description = description; + } + + @VisibleForTesting + void setMembers(String[] members) { + this.members = members; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupDeleteCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupDeleteCommand.java new file mode 100644 index 0000000000..d7effb5229 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupDeleteCommand.java @@ -0,0 +1,80 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; +import java.util.Collections; + +@CommandLine.Command(name = "delete", aliases = "rm") +@ParentCommand(GroupCommand.class) +class GroupDeleteCommand implements Runnable { + + private static final String PROMPT_TEMPLATE = "{{i18n.groupDeletePrompt}}"; + + @CommandLine.Parameters(descriptionKey = "scm.group.delete.group", paramLabel = "name") + private String name; + + @CommandLine.Option(names = {"--yes", "-y"}, descriptionKey = "scm.group.delete.prompt") + private boolean shouldDelete; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @Inject + GroupDeleteCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + if (!shouldDelete) { + templateRenderer.renderToStderr(PROMPT_TEMPLATE, Collections.emptyMap()); + return; + } + Group group = manager.get(name); + if (group != null) { + manager.delete(group); + } + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setShouldDelete(boolean shouldDelete) { + this.shouldDelete = shouldDelete; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupGetCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupGetCommand.java new file mode 100644 index 0000000000..88f8fd8938 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupGetCommand.java @@ -0,0 +1,67 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "get") +class GroupGetCommand implements Runnable{ + + @CommandLine.Parameters(paramLabel = "name") + private String name; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @Inject + GroupGetCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @Override + public void run() { + Group group = manager.get(name); + if (group != null) { + templateRenderer.render(group); + } else { + templateRenderer.renderNotFoundError(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupListCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupListCommand.java new file mode 100644 index 0000000000..f21081897e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupListCommand.java @@ -0,0 +1,100 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.Table; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; +import java.util.Collection; + +import static java.util.stream.Collectors.toList; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "list", aliases = "ls") +class GroupListCommand implements Runnable { + + @CommandLine.Mixin + private final TemplateRenderer templateRenderer; + private final GroupManager manager; + private final GroupCommandBeanMapper beanMapper; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @CommandLine.Option(names = {"--short", "-s"}) + private boolean useShortTemplate; + + private static final String TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{#row.first}}{{#upper}}{{value}}{{/upper}}{{/row.first}}{{^row.first}}{{value}}{{/row.first}}{{^last}} {{/last}}{{/cols}}", + "{{/rows}}" + ); + + private static final String SHORT_TEMPLATE = String.join("\n", + "{{#groups}}", + "{{name}}", + "{{/groups}}" + ); + + @Inject + GroupListCommand(TemplateRenderer templateRenderer, GroupManager manager, GroupCommandBeanMapper beanMapper) { + this.templateRenderer = templateRenderer; + this.manager = manager; + this.beanMapper = beanMapper; + } + + @Override + public void run() { + Collection groupCommandBeans = manager.getAll().stream().map(beanMapper::map).collect(toList()); + if (useShortTemplate) { + templateRenderer.renderToStdout(SHORT_TEMPLATE, ImmutableMap.of("groups", groupCommandBeans)); + } else { + Table table = templateRenderer.createTable(); + table.addHeader("scm.group.name", "scm.group.external"); + String yes = spec.resourceBundle().getString("yes"); + String no = spec.resourceBundle().getString("no"); + for (GroupCommandBean bean : groupCommandBeans) { + table.addRow(bean.getName(), bean.isExternal()? yes: no); + } + templateRenderer.renderToStdout(TABLE_TEMPLATE, ImmutableMap.of("rows", table, "groups", groupCommandBeans)); + } + } + + @VisibleForTesting + void setUseShortTemplate(boolean useShortTemplate) { + this.useShortTemplate = useShortTemplate; + } + + @VisibleForTesting + void setSpec(CommandLine.Model.CommandSpec spec) { + this.spec = spec; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupModifyCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupModifyCommand.java new file mode 100644 index 0000000000..70c2fb49f0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupModifyCommand.java @@ -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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; + +import static java.util.Arrays.asList; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "modify") +class GroupModifyCommand implements Runnable { + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @CommandLine.Parameters(descriptionKey = "scm.group.modify.name") + private String name; + + @CommandLine.Option(names = {"--description", "-d"}, descriptionKey = "scm.group.modify.desc") + private String description; + + @CommandLine.Option(names = {"--member", "-m"}, descriptionKey = "scm.group.modify.member") + private String[] members; + + @CommandLine.Option(names = {"--external", "-e"}, descriptionKey = "scm.group.modify.external") + private Boolean external; + + @Inject + GroupModifyCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + Group existingGroup = manager.get(name); + if (existingGroup == null) { + templateRenderer.renderNotFoundError(); + } else { + if (description != null) { + existingGroup.setDescription(description); + } + if (external != null) { + existingGroup.setExternal(external); + } + if (members != null) { + existingGroup.setMembers(asList(members)); + } + manager.modify(existingGroup); + Group modifiedGroup = manager.get(name); + templateRenderer.render(modifiedGroup); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setDescription(String description) { + this.description = description; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setMembers(String[] members) { + this.members = members; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setExternal(boolean external) { + this.external = external; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupRemoveMemberCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupRemoveMemberCommand.java new file mode 100644 index 0000000000..1e063a6c29 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupRemoveMemberCommand.java @@ -0,0 +1,79 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.repository.cli.GroupCommand; + +import javax.inject.Inject; +import java.util.Arrays; + +@ParentCommand(GroupCommand.class) +@CommandLine.Command(name = "remove-member", aliases = "remove") +class GroupRemoveMemberCommand implements Runnable { + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final GroupManager manager; + + @CommandLine.Parameters(index = "0", arity = "1", descriptionKey = "scm.group.remove-member.name") + private String name; + @CommandLine.Parameters(index = "1..", arity = "1..", descriptionKey = "scm.group.remove-member.members") + private String[] members; + + @Inject + GroupRemoveMemberCommand(GroupTemplateRenderer templateRenderer, GroupManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + Group existingGroup = manager.get(name); + if (existingGroup == null) { + templateRenderer.renderNotFoundError(); + } else { + Arrays.stream(members).forEach(existingGroup::remove); + manager.modify(existingGroup); + Group modifiedGroup = manager.get(name); + templateRenderer.render(modifiedGroup); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setMembers(String[] members) { + this.members = members; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java new file mode 100644 index 0000000000..0184ee096f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java @@ -0,0 +1,75 @@ +/* + * 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.group.cli; + +import com.google.common.collect.ImmutableMap; +import sonia.scm.cli.CliContext; +import sonia.scm.cli.ExitCode; +import sonia.scm.cli.Table; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.group.Group; +import sonia.scm.template.TemplateEngineFactory; + +import javax.inject.Inject; +import java.util.Collections; + +class GroupTemplateRenderer extends TemplateRenderer { + + private static final String NOT_FOUND_TEMPLATE = "{{i18n.groupNotFound}}"; + + private static final String DETAILS_TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{value}}{{/cols}}", + "{{/rows}}" + ); + + private final GroupCommandBeanMapper mapper; + + @Inject + GroupTemplateRenderer(CliContext context, TemplateEngineFactory templateEngineFactory, GroupCommandBeanMapper mapper) { + super(context, templateEngineFactory); + this.mapper = mapper; + } + + void render(Group group) { + GroupCommandBean groupBean = mapper.map(group); + Table table = createTable(); + String yes = getBundle().getString("yes"); + String no = getBundle().getString("no"); + table.addLabelValueRow("scm.group.name", groupBean.getName()); + table.addLabelValueRow("scm.group.description", groupBean.getDescription()); + table.addLabelValueRow("scm.group.members", groupBean.getMembersList()); + table.addLabelValueRow("scm.group.external", groupBean.isExternal() ? yes : no); + table.addLabelValueRow("creationDate", groupBean.getCreationDate()); + table.addLabelValueRow("lastModified", groupBean.getLastModified()); + + renderToStdout(DETAILS_TABLE_TEMPLATE, ImmutableMap.of("rows", table, "repo", groupBean)); + } + + void renderNotFoundError() { + renderToStderr(NOT_FOUND_TEMPLATE, Collections.emptyMap()); + getContext().exit(ExitCode.NOT_FOUND); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryGetCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryGetCommand.java index c07e0ff793..4a28df768b 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryGetCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryGetCommand.java @@ -51,7 +51,7 @@ class RepositoryGetCommand implements Runnable { } @VisibleForTesting - public void setRepository(String repository) { + void setRepository(String repository) { this.repository = repository; } diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserActivateCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserActivateCommand.java new file mode 100644 index 0000000000..4cde5c4a1c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserActivateCommand.java @@ -0,0 +1,67 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "activate") +class UserActivateCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @Inject + UserActivateCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + User user = manager.get(username); + + if (user != null) { + if (user.isExternal()) { + templateRenderer.renderExternalActivateError(); + } else { + user.setActive(true); + manager.modify(user); + templateRenderer.render(user); + } + } else { + templateRenderer.renderNotFoundError(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBean.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBean.java new file mode 100644 index 0000000000..b3a7b618d2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBean.java @@ -0,0 +1,44 @@ +/* + * 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.user.cli; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +class UserCommandBean { + + private String name; + private String displayName; + private String mail; + private boolean external; + private boolean active; + private String creationDate; + private String lastModified; + +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBeanMapper.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBeanMapper.java new file mode 100644 index 0000000000..40ffe0e303 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCommandBeanMapper.java @@ -0,0 +1,45 @@ +/* + * 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.user.cli; + +import org.mapstruct.Mapper; +import sonia.scm.user.User; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +@Mapper +interface UserCommandBeanMapper { + + UserCommandBean map(User modelObject); + + default String mapTimestampToISODate(Long timestamp) { + if (timestamp != null) { + return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(timestamp)); + } + return null; + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToExternalCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToExternalCommand.java new file mode 100644 index 0000000000..fc78192d58 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToExternalCommand.java @@ -0,0 +1,63 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "convert-to-external", aliases = "conv-ext") +class UserConvertToExternalCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @Inject + UserConvertToExternalCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + User user = manager.get(username); + + if (user != null) { + user.setExternal(true); + manager.modify(user); + templateRenderer.render(user); + } else { + templateRenderer.renderNotFoundError(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToInternalCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToInternalCommand.java new file mode 100644 index 0000000000..90158a3616 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserConvertToInternalCommand.java @@ -0,0 +1,74 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "convert-to-internal", aliases = "conv-int") +class UserConvertToInternalCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @CommandLine.Parameters(index = "1", paramLabel = "", descriptionKey = "scm.user.password") + private String password; + + @Inject + UserConvertToInternalCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + User user = manager.get(username); + + if (user != null) { + user.setExternal(false); + user.setPassword(password); + manager.modify(user); + templateRenderer.render(user); + } else { + templateRenderer.renderNotFoundError(); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setPassword(String password) { + this.password = password; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserCreateCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCreateCommand.java new file mode 100644 index 0000000000..bdbd20a2a2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserCreateCommand.java @@ -0,0 +1,133 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.CommandValidator; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import javax.validation.constraints.Email; + +@CommandLine.Command(name = "create") +@ParentCommand(value = UserCommand.class) +class UserCreateCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + @CommandLine.Mixin + private final CommandValidator validator; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @CommandLine.Parameters(index = "1", paramLabel = "", descriptionKey = "scm.user.displayName") + private String displayName; + + @Email + @CommandLine.Option(names = {"--email", "-e"}, descriptionKey = "scm.user.email") + private String email; + + @CommandLine.Option(names = {"--external", "-x"}, descriptionKey = "scm.user.create.external") + private boolean external; + + @CommandLine.Option(names = {"--password", "-p"}, descriptionKey = "scm.user.password") + private String password; + + @CommandLine.Option(names = {"--deactivate", "-d"}, descriptionKey = "scm.user.create.deactivate") + private boolean inactive; + + @Inject + public UserCreateCommand(UserTemplateRenderer templateRenderer, + CommandValidator validator, + UserManager manager) { + this.templateRenderer = templateRenderer; + this.validator = validator; + this.manager = manager; + } + + @Override + public void run() { + validator.validate(); + User newUser = new User(); + newUser.setName(username); + newUser.setDisplayName(displayName); + newUser.setMail(email); + newUser.setExternal(external); + if (!external) { + if (password == null) { + templateRenderer.renderPasswordError(); + } + newUser.setPassword(password); + newUser.setActive(!inactive); + } else { + if (inactive) { + templateRenderer.renderExternalDeactivateError(); + } + } + User createdUser = manager.create(newUser); + templateRenderer.render(createdUser); + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setUsername(String username) { + this.username = username; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setEmail(String email) { + this.email = email; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setExternal(boolean external) { + this.external = external; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setPassword(String password) { + this.password = password; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setInactive(boolean inactive) { + this.inactive = inactive; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeactivateCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeactivateCommand.java new file mode 100644 index 0000000000..b3f9902d50 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeactivateCommand.java @@ -0,0 +1,67 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "deactivate") +class UserDeactivateCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @Inject + UserDeactivateCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + User user = manager.get(username); + + if (user != null) { + if (user.isExternal()) { + templateRenderer.renderExternalDeactivateError(); + } else { + user.setActive(false); + manager.modify(user); + templateRenderer.render(user); + } + } else { + templateRenderer.renderNotFoundError(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeleteCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeleteCommand.java new file mode 100644 index 0000000000..0c2febc0a0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserDeleteCommand.java @@ -0,0 +1,77 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Collections; + +@CommandLine.Command(name = "delete", aliases = "rm") +@ParentCommand(UserCommand.class) +class UserDeleteCommand implements Runnable { + + private static final String CONFIRM_TEMPLATE = "{{i18n.scmUserDeleteConfirm}}"; + private static final String SUCCESS_TEMPLATE = "{{i18n.scmUserDeleteSuccess}}"; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @CommandLine.Option(names = {"--yes", "-y"}, descriptionKey = "scm.user.delete.prompt") + private boolean shouldDelete; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @Inject + public UserDeleteCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + if (!shouldDelete) { + templateRenderer.renderToStderr(CONFIRM_TEMPLATE, Collections.emptyMap()); + return; + } + User user = manager.get(username); + if (user != null) { + manager.delete(user); + templateRenderer.renderToStdout(SUCCESS_TEMPLATE, Collections.emptyMap()); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setShouldDelete(boolean shouldDelete) { + this.shouldDelete = shouldDelete; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserGetCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserGetCommand.java new file mode 100644 index 0000000000..cd269c3631 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserGetCommand.java @@ -0,0 +1,60 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "get") +class UserGetCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final UserManager manager; + + @Inject + UserGetCommand(UserTemplateRenderer templateRenderer, UserManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + @Override + public void run() { + User user = manager.get(username); + if (user != null) { + templateRenderer.render(user); + } else { + templateRenderer.renderNotFoundError(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserListCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserListCommand.java new file mode 100644 index 0000000000..2955b6c2dd --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserListCommand.java @@ -0,0 +1,99 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.Table; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.stream.Collectors; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "list", aliases = "ls") +class UserListCommand implements Runnable { + + @CommandLine.Mixin + private final TemplateRenderer templateRenderer; + private final UserManager manager; + private final UserCommandBeanMapper mapper; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @CommandLine.Option(names = {"--short", "-s"}) + private boolean useShortTemplate; + + private static final String TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{#row.first}}{{#upper}}{{value}}{{/upper}}{{/row.first}}{{^row.first}}{{value}}{{/row.first}}{{^last}} {{/last}}{{/cols}}", + "{{/rows}}" + ); + + private static final String SHORT_TEMPLATE = String.join("\n", + "{{#users}}", + "{{name}}", + "{{/users}}" + ); + + @Inject + public UserListCommand(TemplateRenderer templateRenderer, UserManager manager, UserCommandBeanMapper mapper) { + this.templateRenderer = templateRenderer; + this.manager = manager; + this.mapper = mapper; + } + + @Override + public void run() { + Collection beans = manager.getAll().stream().map(mapper::map).collect(Collectors.toList()); + if (useShortTemplate) { + templateRenderer.renderToStdout(SHORT_TEMPLATE, ImmutableMap.of("users", beans)); + } else { + Table table = templateRenderer.createTable(); + String yes = spec.resourceBundle().getString("yes"); + String no = spec.resourceBundle().getString("no"); + table.addHeader("scm.user.username", "scm.user.displayName", "scm.user.email", "scm.user.external", "scm.user.active"); + for (UserCommandBean bean : beans) { + table.addRow(bean.getName(), bean.getDisplayName(), bean.getMail(), bean.isExternal() ? yes : no, bean.isActive() ? yes : no); + } + templateRenderer.renderToStdout(TABLE_TEMPLATE, ImmutableMap.of("rows", table, "users", beans)); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setUseShortTemplate(boolean useShortTemplate) { + this.useShortTemplate = useShortTemplate; + } + + @VisibleForTesting + void setSpec(CommandLine.Model.CommandSpec spec) { + this.spec = spec; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserModifyCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserModifyCommand.java new file mode 100644 index 0000000000..cf4c617bac --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserModifyCommand.java @@ -0,0 +1,107 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.CommandValidator; +import sonia.scm.cli.ParentCommand; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import javax.validation.constraints.Email; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "modify") +class UserModifyCommand implements Runnable { + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + @CommandLine.Mixin + private final CommandValidator validator; + private final UserManager manager; + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String username; + + @CommandLine.Option(names = {"--displayname", "-d"}, descriptionKey = "scm.user.displayName") + private String displayName; + + @Email + @CommandLine.Option(names = {"--email", "-e"}, descriptionKey = "scm.user.email") + private String email; + + @CommandLine.Option(names = {"--password", "-p"}, descriptionKey = "scm.user.password") + private String password; + + @Inject + UserModifyCommand(UserTemplateRenderer templateRenderer, CommandValidator validator, UserManager manager) { + this.templateRenderer = templateRenderer; + this.validator = validator; + this.manager = manager; + } + + @Override + public void run() { + validator.validate(); + + User user = manager.get(username); + + if (user != null) { + if (displayName != null) { + user.setDisplayName(displayName); + } + if (email != null) { + user.setMail(email); + } + if (password != null) { + user.setPassword(password); + } + manager.modify(user); + templateRenderer.render(user); + } else { + templateRenderer.renderNotFoundError(); + } + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setEmail(String email) { + this.email = email; + } + + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setPassword(String password) { + this.password = password; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java new file mode 100644 index 0000000000..66d7a1f42d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java @@ -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.user.cli; + +import com.google.common.collect.ImmutableMap; +import sonia.scm.cli.CliContext; +import sonia.scm.cli.ExitCode; +import sonia.scm.cli.Table; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.template.TemplateEngineFactory; +import sonia.scm.user.User; + +import javax.inject.Inject; +import java.util.Collections; + +class UserTemplateRenderer extends TemplateRenderer { + + private static final String DETAILS_TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{value}}{{/cols}}", + "{{/rows}}" + ); + private static final String PASSWORD_ERROR_TEMPLATE = "{{i18n.scmUserErrorPassword}}"; + private static final String EXTERNAL_ACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalActivate}}"; + private static final String EXTERNAL_DEACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalDeactivate}}"; + private static final String NOT_FOUND_TEMPLATE = "{{i18n.scmUserErrorNotFound}}"; + + private final CliContext context; + private final UserCommandBeanMapper mapper; + + @Inject + UserTemplateRenderer(CliContext context, TemplateEngineFactory templateEngineFactory, UserCommandBeanMapper mapper) { + super(context, templateEngineFactory); + this.context = context; + this.mapper = mapper; + } + + public void render(User user) { + Table table = createTable(); + + String yes = getBundle().getString("yes"); + String no = getBundle().getString("no"); + UserCommandBean bean = mapper.map(user); + table.addLabelValueRow("scm.user.username", bean.getName()); + table.addLabelValueRow("scm.user.displayName", bean.getDisplayName()); + table.addLabelValueRow("scm.user.email", bean.getMail()); + table.addLabelValueRow("scm.user.external", bean.isExternal() ? yes : no); + table.addLabelValueRow("scm.user.active", bean.isActive() ? yes : no); + table.addLabelValueRow("creationDate", bean.getCreationDate()); + table.addLabelValueRow("lastModified", bean.getLastModified()); + renderToStdout(DETAILS_TABLE_TEMPLATE, ImmutableMap.of("rows", table, "user", bean)); + } + + public void renderPasswordError() { + renderToStderr(PASSWORD_ERROR_TEMPLATE, Collections.emptyMap()); + context.exit(ExitCode.USAGE); + } + + public void renderExternalActivateError() { + renderToStderr(EXTERNAL_ACTIVATE_TEMPLATE, Collections.emptyMap()); + context.exit(ExitCode.USAGE); + } + + public void renderExternalDeactivateError() { + renderToStderr(EXTERNAL_DEACTIVATE_TEMPLATE, Collections.emptyMap()); + context.exit(ExitCode.USAGE); + } + + public void renderNotFoundError() { + renderToStderr(NOT_FOUND_TEMPLATE, Collections.emptyMap()); + context.exit(ExitCode.NOT_FOUND); + } +} diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties index d147dd10f5..ab1ff480e1 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties @@ -28,6 +28,11 @@ errorLabel= ERROR errorExecutionFailed = Error transactionId = Transaction ID +creationDate = Creation Date +lastModified = Last Modified +yes = yes +no = no + repoNamespace = Namespace repoName = Name repoDescription= Description @@ -54,12 +59,12 @@ scm.logout.usage.description.0 = Removes the api key from server and local cli c scm.ping.usage.description.0 = Returns PONG if the server is available ## Repo -scm.repo.usage.description.0 = Parent command for all repository-related actions +scm.repo.usage.description.0 = Resource command for all repository-related actions repoNotFound= Could not find repository repoInvalidInput= Invalid input. Use namespace/name ### Get repo -scm.repo.get.usage.description.0 = Returns repository related information +scm.repo.get.usage.description.0 = Returns repository-related information ### List repo scm.repo.list.usage.description.0 = List all repositories on server @@ -81,3 +86,102 @@ repoDeletePrompt= If you really want to delete this repository please pass --yes ### Modify repo scm.repo.modify.usage.description.0 = Modify repository on server scm.repo.modify.repository = Repository namespace/name + +## User +scm.user.username = Username +scm.user.displayName = Display Name +scm.user.email = Email address +scm.user.external = External +scm.user.password = Password +scm.user.active = Active + +### User error +scmUserErrorPassword = Password is required for internal users +scmUserErrorExternalActivate = External users cannot be activated +scmUserErrorExternalDeactivate = External users cannot be deactivated +scmUserErrorNotFound = Could not find user + +### User command +scm.user.usage.description.0 = Resource command for all user-related actions + +### Get user +scm.user.get.usage.description.0 = Returns user-related information + +### List user +scm.user.list.usage.description.0 = List all users on server + +### Create user +scm.user.create.usage.description.0 = Creates new user on server +scm.user.create.deactivate = User is deactivated +scm.user.create.external = User is managed by an external system + +### Delete user +scm.user.delete.usage.description.0 = Deletes user on server +scm.user.delete.prompt = Set this flag to agree to delete the user +scmUserDeleteConfirm = If you really want to delete this user please pass --yes +scmUserDeleteSuccess = Successfully deleted user + +### Modify user +scm.user.modify.usage.description.0 = Modify user on server + +### Activate user +scm.user.activate.usage.description.0 = Activate user on server + +### Deactivate user +scm.user.deactivate.usage.description.0 = Deactivate user on server + +### Convert to external user +scm.user.convert-to-external.usage.description.0 = Convert user to external user on server + +### Convert to internal user +scm.user.convert-to-internal.usage.description.0 = Convert user to internal user on server + +### Group +scm.group.name = Name +scm.group.description = Description +scm.group.members = Members +scm.group.membersList = Members +scm.group.external = External + +### Group command +scm.group.usage.description.0 = Resource command for all group-related actions + +### Get group +scm.group.get.usage.description.0 = Returns group-related information +scm.group.get.name = Name of the group + +### List group +scm.group.list.usage.description.0 = List all groups on server +scm.group.list.short = List only names of groups + +### Create group +scm.group.create.usage.description.0 = Creates new group on server +scm.group.create.name = Name of the group +scm.group.create.description = Description of the new group +scm.group.create.member = Members of the new group +scm.group.create.external = Group is managed by an external system + +### Delete group +scm.group.delete.usage.description.0 = Delete group on server +scm.group.delete.group = Name of the group +scm.group.delete.prompt = Set this flag to agree to delete the group +groupDeletePrompt = If you really want to delete this group please pass --yes + +### Modify group +scm.group.modify.usage.description.0 = Modify group on server +scm.group.modify.name = Name of the group +scm.group.modify.desc = New description of the group +scm.group.modify.member = New members of the group +scm.group.modify.external = Whether the group is an external group or not + +### Add members to group +scm.group.add-member.usage.description.0 = Add members to an existing group +scm.group.add-member.name = Name of the group +scm.group.add-member.members = Members to add to the group + +### Remove members form group +scm.group.remove-member.usage.description.0 = Remove members from an existing group +scm.group.remove-member.name = Name of the group +scm.group.remove-member.members = Members to remove from the group + +groupNotFound = Could not find group diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties index b4c28a8b3c..f84c86f992 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties @@ -28,6 +28,11 @@ errorLabel= FEHLER errorExecutionFailed = Fehler transactionId = Transaktions-ID +creationDate = Erstellt +lastModified = Zuletzt bearbeitet +yes = ja +no = nein + repoNamespace = Namespace repoName = Name repoDescription= Beschreibung @@ -54,7 +59,7 @@ scm.logout.usage.description.0 = Entfernt den API Key vom Server und die lokale scm.ping.usage.description.0 = Antwortet PONG, wenn der Server erreichbar ist ## Repo -scm.repo.usage.description.0 = Gruppen Befehl für alle Repository Befehle +scm.repo.usage.description.0 = Ressourcenkommando für alle Repositoryaktionen repoNotFound= Repository konnte nicht gefunden werden repoInvalidInput= Ungültige Eingabe. Nutzen Sie namespace/name @@ -81,3 +86,102 @@ repoDeletePrompt= Wenn dieses Repository endg ### Modify repo scm.repo.modify.usage.description.0 = Aktualisiert ein Repository auf dem Server scm.repo.modify.repository = Repository Namespace/Name + +## User +scm.user.username = Benutzername +scm.user.displayName = Anzeigename +scm.user.email = E-Mail-Adresse +scm.user.external = Extern +scm.user.password = Passwort +scm.user.active = Aktiv + +### User error +scmUserErrorPassword = Für interne Benutzer muss ein Passwort gesetzt werden +scmUserErrorExternalActivate = Externe Benutzer können nicht aktiviert werden +scmUserErrorExternalDeactivate = Externe Benutzer können nicht deaktiviert werden +scmUserErrorNotFound= Benutzer konnte nicht gefunden werden + +### User command +scm.user.usage.description.0 = Ressourcenkommando für alle Benutzeraktionen + +### Get user +scm.user.get.usage.description.0 = Liefert Informationen zum Benutzer + +### List user +scm.user.list.usage.description.0 = Listet alle Benutzer auf dem Server + +### Create user +scm.user.create.usage.description.0 = Erzeugt einen neuen Benutzer auf dem Server +scm.user.create.deactivate = Benutzer ist deaktiviert +scm.user.create.external = Der Benutzer wird von einem Fremdsystem verwaltet + +### Delete user +scm.user.delete.usage.description.0 = Löscht einen Benutzer auf dem Server +scm.user.delete.prompt = Setzen Sie diese Option, um einen Benutzer endgültig zu löschen +scmUserDeleteConfirm = Wenn dieser Benutzer endgültig gelöscht werden soll, setzen Sie bitte --yes +scmUserDeleteSuccess = Benutzer erfolgreich gelöscht + +### Modify user +scm.user.modify.usage.description.0 = Aktualisiert einen Benutzer auf dem Server + +### Activate user +scm.user.activate.usage.description.0 = Aktiviert einen Benutzer auf dem Server + +### Deactivate user +scm.user.deactivate.usage.description.0 = Deaktiviert einen Benutzer auf dem Server + +### Convert to external user +scm.user.convert-to-external.usage.description.0 = Konvertiert einen Benutzer zu einem externen Benutzer + +### Convert to internal user +scm.user.convert-to-internal.usage.description.0 = Konvertiert einen Benutzer zu einem internen Benutzer + +### Group +scm.group.name = Name +scm.group.description = Beschreibung +scm.group.members = Mitglieder +scm.group.membersList = Mitglieder +scm.group.external = Extern + +### Group command +scm.group.usage.description.0 = Ressourcenkommando für alle Gruppenaktionen + +### Get group +scm.group.get.usage.description.0 = Liefert Informationen zur Gruppe +scm.group.get.name = Name der Gruppe + +### List group +scm.group.list.usage.description.0 = Listet alle Gruppen auf dem Server +scm.group.list.short = Listet nur die Namen der Gruppen + +### Create group +scm.group.create.usage.description.0 = Erzeugt eine neue Gruppe auf dem Server +scm.group.create.name = Name der Gruppe +scm.group.create.description = Beschreibung der Gruppe +scm.group.create.member = Mitglieder der Gruppe +scm.group.create.external = Die Gruppe wird von einem externen System verwaltet + +### Delete group +scm.group.delete.usage.description.0 = Löscht eine Gruppe auf dem Server +scm.group.delete.group = Name der Gruppe +scm.group.delete.prompt = Setzen Sie diese Option, um eine Gruppe endgültig zu löschen +groupDeletePrompt = Wenn diese Gruppe endgültig gelöscht werden soll, setzen Sie bitte --yes + +### Modify group +scm.group.modify.usage.description.0 = Ändert eine Gruppe auf dem Server +scm.group.modify.name = Name der Gruppe +scm.group.modify.desc = Neue Beschreibung der Gruppe +scm.group.modify.member = Neue Mitglieder der Gruppe +scm.group.modify.external = Flag, ob diese Gruppe von einem Fremdsystem verwaltet wird + +### Add members to group +scm.group.add-member.usage.description.0 = Fügt neue Mitglieder zu einer Gruppe hinzu +scm.group.add-member.name = Name der Gruppe +scm.group.add-member.members = Hinzuzufügende Mitglieder + +### Remove members form group +scm.group.remove-member.usage.description.0 = Entfernt Mitglieder von einer Gruppe +scm.group.remove-member.name = Name der Gruppe +scm.group.remove-member.members = Zu löschende Mitglieder + +groupNotFound = Gruppe konnte nicht gefunden werden diff --git a/scm-webapp/src/test/java/sonia/scm/cli/TemplateTestRenderer.java b/scm-webapp/src/test/java/sonia/scm/cli/TemplateTestRenderer.java new file mode 100644 index 0000000000..f262c466d6 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/cli/TemplateTestRenderer.java @@ -0,0 +1,117 @@ +/* + * 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.io.Resources; +import picocli.CommandLine; +import sonia.scm.template.MustacheTemplateTestEngine; +import sonia.scm.template.TemplateEngine; +import sonia.scm.template.TemplateEngineFactory; + +import javax.servlet.ServletContext; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.Locale; +import java.util.ResourceBundle; + +import static java.util.Collections.singleton; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This is a helper to create a {@link TemplateRenderer} that can be used for + * command tests + */ +public class TemplateTestRenderer { + + private final ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); + private final ByteArrayOutputStream stdErr = new ByteArrayOutputStream(); + private final CliContext context = mock(CliContext.class); + private final TemplateEngineFactory templateEngineFactory; + + @SuppressWarnings("UnstableApiUsage") + public TemplateTestRenderer() { + lenient().when(context.getStdout()).thenReturn(new PrintWriter(stdOut)); + lenient().when(context.getStderr()).thenReturn(new PrintWriter(stdErr)); + lenient().doThrow(CliExitException.class).when(context).exit(anyInt()); + + ServletContext servletContext = mock(ServletContext.class); + lenient().when(servletContext.getResourceAsStream(any())) + .thenAnswer(invocation -> Resources.getResource(invocation.getArgument(0, String.class)).openStream()); + TemplateEngine engine = new MustacheTemplateTestEngine().createEngine(servletContext); + + when(context.getLocale()).thenReturn(new Locale("en")); + + templateEngineFactory = new TemplateEngineFactory(singleton(engine), engine); + } + + public TemplateRenderer createTemplateRenderer() { + return new TemplateRenderer(context, templateEngineFactory) { + @Override + protected ResourceBundle getBundle() { + return TemplateTestRenderer.this.getResourceBundle(); + } + }; + } + + public ResourceBundle getResourceBundle() { + return ResourceBundle.getBundle("sonia.scm.cli.i18n", context.getLocale(), new ResourceBundle.Control() { + @Override + public Locale getFallbackLocale(String baseName, Locale locale) { + return Locale.ROOT; + } + }); + } + + public void setLocale(String locale) { + lenient().when(context.getLocale()).thenReturn(new Locale(locale)); + } + + public String getStdOut() { + return stdOut.toString(); + } + + public String getStdErr() { + return stdErr.toString(); + } + + public CliContext getContextMock() { + return context; + } + + public TemplateEngineFactory getTemplateEngineFactory() { + return templateEngineFactory; + } + + public CommandLine.Model.CommandSpec getMockedSpeck() { + ResourceBundle resourceBundle = getResourceBundle(); + CommandLine.Model.CommandSpec mock = mock(CommandLine.Model.CommandSpec.class); + lenient().when(mock.resourceBundle()).thenReturn(resourceBundle); + return mock; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java new file mode 100644 index 0000000000..ae4e529456 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java @@ -0,0 +1,122 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.Assertions; +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.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupAddMemberCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + + private GroupAddMemberCommand command; + + @BeforeEach + void initCommand() { + command = new GroupAddMemberCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulModificationTest { + + private Group group; + + @BeforeEach + void mockModification() { + group = new Group("test", "hog", "zaphod"); + + when(manager.get("hog")).thenAnswer(invocation -> group); + doAnswer(invocation -> { + Group modifiedGroup = invocation.getArgument(0, Group.class); + modifiedGroup.setLastModified(1649662000000L); + group = modifiedGroup; + return null; + }).when(manager).modify(any()); + + command.setName("hog"); + command.setMembers(new String[]{"trillian", "arthur", "ford"}); + } + + @Test + void shouldModifyGroup() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("hog"); + assertThat(argument.getMembers()).contains("zaphod", "trillian", "arthur", "ford"); + return true; + })); + } + + @Test + void shouldPrintGroupAfterModification() { + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Name: hog", + "Members: zaphod, trillian, arthur, ford", + "Last Modified: 2022-04-11T07:26:40Z" + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + } + + @Test + void shouldFailIfGroupDoesNotExists() { + when(manager.get("hog")).thenReturn(null); + command.setName("hog"); + + Assertions.assertThrows( + CliExitException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEqualTo("Could not find group"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupCreateCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupCreateCommandTest.java new file mode 100644 index 0000000000..b3d8d139b1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupCreateCommandTest.java @@ -0,0 +1,103 @@ +/* + * 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.group.cli; + +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.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupCreateCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + + private GroupCreateCommand command; + + @BeforeEach + void initCommand() { + command = new GroupCreateCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulCreationTest { + + @BeforeEach + void mockCreation() { + when(manager.create(any())) + .thenAnswer(invocation -> { + Group createdGroup = invocation.getArgument(0, Group.class); + createdGroup.setCreationDate(1649262000000L); + return createdGroup; + }); + + command.setName("hog"); + command.setDescription("Crew of the Heart of Gold"); + command.setMembers(new String[]{"zaphod", "trillian"}); + } + + @Test + void shouldCreateGroup() { + command.run(); + + verify(manager).create(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("hog"); + assertThat(argument.getDescription()).isEqualTo("Crew of the Heart of Gold"); + assertThat(argument.getMembers()).contains("zaphod", "trillian"); + return true; + })); + } + + @Test + void shouldPrintGroupAfterCreation() { + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Name: hog", + "Description: Crew of the Heart of Gold", + "Members: zaphod, trillian", + "External: no", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: " + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupDeleteCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupDeleteCommandTest.java new file mode 100644 index 0000000000..8b17e97838 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupDeleteCommandTest.java @@ -0,0 +1,98 @@ +/* + * 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.group.cli; + +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.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupDeleteCommandTest { + + @Mock + private GroupManager manager; + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + private GroupDeleteCommand command; + + @BeforeEach + void initCommand() { + command = new GroupDeleteCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Test + void shouldRenderPromptWithoutYesFlag() { + command.setName("hog"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEqualTo("If you really want to delete this group please pass --yes"); + } + + @Test + void shouldDeleteGroup() { + Group groupToDelete = new Group("test", "vogons"); + when(manager.get("vogons")).thenReturn(groupToDelete); + command.setShouldDelete(true); + command.setName("vogons"); + + command.run(); + + verify(manager).delete(groupToDelete); + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + + @Test + void shouldNotFailForNotExistingGroup() { + when(manager.get("vogons")).thenReturn(null); + command.setShouldDelete(true); + command.setName("vogons"); + + command.run(); + + verify(manager, never()).delete(any()); + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java new file mode 100644 index 0000000000..69cd39683b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java @@ -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.group.cli; + +import org.junit.jupiter.api.Assertions; +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.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupGetCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + + private GroupGetCommand command; + + @BeforeEach + void initCommand() { + command = new GroupGetCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Test + void shouldGetGroup() { + Group group = new Group("test", "hog", "zaphod", "trillian"); + group.setCreationDate(1649262000000L); + group.setLastModified(1649462000000L); + group.setDescription("Crew of the Heart of Gold"); + + when(manager.get("hog")).thenReturn(group); + + command.setName("hog"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Name: hog", + "Description: Crew of the Heart of Gold", + "Members: zaphod, trillian", + "External: no", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-08T23:53:20Z" + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + + @Test + void shouldFailForNotExistingGroup() { + command.setName("hog"); + + Assertions.assertThrows( + CliExitException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .contains("Could not find group"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupListCommandTest.java new file mode 100644 index 0000000000..d9765bf62d --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupListCommandTest.java @@ -0,0 +1,85 @@ +/* + * 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.group.cli; + +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.cli.TemplateTestRenderer; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupListCommandTest { + + @Mock + private GroupManager manager; + + private final GroupCommandBeanMapper beanMapper = new GroupCommandBeanMapperImpl(); + private final TemplateTestRenderer testRenderer = new TemplateTestRenderer(); + + private GroupListCommand command; + + @BeforeEach + void initCommand() { + command = new GroupListCommand(testRenderer.createTemplateRenderer(), manager, beanMapper); + command.setSpec(testRenderer.getMockedSpeck()); + } + + @BeforeEach + void mockGroups() { + Group internalGroup = new Group("test", "hog"); + Group externalGroup = new Group("test", "vogons"); + externalGroup.setExternal(true); + when(manager.getAll()) + .thenReturn( + asList( + internalGroup, + externalGroup)); + } + + @Test + void shouldRenderShortTable() { + command.setUseShortTemplate(true); + + command.run(); + + assertThat(testRenderer.getStdOut()).isEqualTo("hog\nvogons\n"); + } + + @Test + void shouldRenderLongTable() { + command.run(); + + assertThat(testRenderer.getStdOut()) + .isEqualTo("NAME EXTERNAL\nhog no \nvogons yes \n"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java new file mode 100644 index 0000000000..baab3461f1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java @@ -0,0 +1,180 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.Assertions; +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.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupModifyCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + + private GroupModifyCommand command; + + @BeforeEach + void initCommand() { + command = new GroupModifyCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulModificationTest { + + private Group group; + + @BeforeEach + void mockModification() { + group = new Group("test", "hog", "zaphod", "trillian"); + group.setCreationDate(1649262000000L); + group.setLastModified(1649462000000L); + group.setDescription("Crew of the Heart of Gold"); + + when(manager.get("hog")).thenAnswer(invocation -> group); + doAnswer(invocation -> { + Group modifiedGroup = invocation.getArgument(0, Group.class); + modifiedGroup.setLastModified(1649662000000L); + group = modifiedGroup; + return null; + }).when(manager).modify(any()); + + command.setName("hog"); + } + + @Test + void shouldModifyGroup() { + command.setDescription("Earthlings on the Heart of Gold"); + command.setMembers(new String[]{"arthur", "trillian"}); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("hog"); + assertThat(argument.getDescription()).isEqualTo("Earthlings on the Heart of Gold"); + assertThat(argument.getMembers()).contains("arthur", "trillian"); + assertThat(argument.isExternal()).isFalse(); + return true; + })); + } + + @Test + void shouldNotModifyDescriptionIfNotSet() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getDescription()).isEqualTo("Crew of the Heart of Gold"); + return true; + })); + } + + @Test + void shouldNotModifyExternalIfNotSet() { + group.setExternal(true); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.isExternal()).isTrue(); + return true; + })); + } + + @Test + void shouldNotModifyMembersIfNotSet() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getMembers()).contains("zaphod", "trillian"); + return true; + })); + } + + @Test + void shouldSetGroupExternal() { + command.setExternal(true); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getMembers()).isEmpty(); + assertThat(argument.isExternal()).isTrue(); + return true; + })); + } + + @Test + void shouldPrintGroupAfterModification() { + command.setDescription("Earthlings on the Heart of Gold"); + command.setMembers(new String[]{"arthur", "trillian"}); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Name: hog", + "Description: Earthlings on the Heart of Gold", + "Members: arthur, trillian", + "External: no", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-11T07:26:40Z" + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + } + + @Test + void shouldFailIfGroupDoesNotExists() { + when(manager.get("hog")).thenReturn(null); + command.setName("hog"); + + Assertions.assertThrows( + CliExitException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEqualTo("Could not find group"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java new file mode 100644 index 0000000000..9a5e85a470 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java @@ -0,0 +1,122 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.Assertions; +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.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupRemoveMemberCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + + private GroupRemoveMemberCommand command; + + @BeforeEach + void initCommand() { + command = new GroupRemoveMemberCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulModificationTest { + + private Group group; + + @BeforeEach + void mockModification() { + group = new Group("test", "hog", "zaphod", "marvin", "trillian"); + + when(manager.get("hog")).thenAnswer(invocation -> group); + doAnswer(invocation -> { + Group modifiedGroup = invocation.getArgument(0, Group.class); + modifiedGroup.setLastModified(1649662000000L); + group = modifiedGroup; + return null; + }).when(manager).modify(any()); + + command.setName("hog"); + command.setMembers(new String[]{"zaphod", "marvin"}); + } + + @Test + void shouldModifyGroup() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("hog"); + assertThat(argument.getMembers()).contains("trillian"); + return true; + })); + } + + @Test + void shouldPrintGroupAfterModification() { + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Name: hog", + "Members: trillian", + "Last Modified: 2022-04-11T07:26:40Z" + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + } + + @Test + void shouldFailIfGroupDoesNotExists() { + when(manager.get("hog")).thenReturn(null); + command.setName("hog"); + + Assertions.assertThrows( + CliExitException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()) + .isEmpty(); + assertThat(testRenderer.getStdErr()) + .isEqualTo("Could not find group"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupTemplateTestRenderer.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupTemplateTestRenderer.java new file mode 100644 index 0000000000..f115c14e68 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupTemplateTestRenderer.java @@ -0,0 +1,52 @@ +/* + * 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.group.cli; + +import sonia.scm.cli.TemplateTestRenderer; + +import java.util.ResourceBundle; + +class GroupTemplateTestRenderer { + private final TemplateTestRenderer testRenderer = new TemplateTestRenderer(); + private final GroupCommandBeanMapper beanMapper = new GroupCommandBeanMapperImpl(); + private final GroupTemplateRenderer templateRenderer = new GroupTemplateRenderer(testRenderer.getContextMock(), testRenderer.getTemplateEngineFactory(), beanMapper) { + @Override + protected ResourceBundle getBundle() { + return testRenderer.getResourceBundle(); + } + }; + + GroupTemplateRenderer getTemplateRenderer() { + return templateRenderer; + } + + String getStdOut() { + return testRenderer.getStdOut(); + } + + String getStdErr() { + return testRenderer.getStdErr(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTestEngine.java b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTestEngine.java new file mode 100644 index 0000000000..36f4bf9992 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTestEngine.java @@ -0,0 +1,54 @@ +/* + * 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.template; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import sonia.scm.plugin.PluginLoader; + +import javax.servlet.ServletContext; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class can be used to create a mustache renderer in unit test + */ +public class MustacheTemplateTestEngine { + + public TemplateEngine createEngine(ServletContext context) { + PluginLoader loader = mock(PluginLoader.class); + + when(loader.getUberClassLoader()).thenReturn( + Thread.currentThread().getContextClassLoader()); + + MustacheTemplateEngine.PluginLoaderHolder pluginLoaderHolder = new MustacheTemplateEngine.PluginLoaderHolder(); + pluginLoaderHolder.pluginLoader = loader; + + MustacheTemplateEngine.MeterRegistryHolder meterRegistryHolder = new MustacheTemplateEngine.MeterRegistryHolder(); + meterRegistryHolder.registry = new SimpleMeterRegistry(); + + return new MustacheTemplateEngine(context, pluginLoaderHolder, meterRegistryHolder); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserActivateCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserActivateCommandTest.java new file mode 100644 index 0000000000..dcb5225b1a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserActivateCommandTest.java @@ -0,0 +1,188 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserActivateCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserActivateCommand command; + + @BeforeEach + void initCommand() { + command = new UserActivateCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulActivationTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(false); + user.setActive(false); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldActivateInternalUser() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldPrintUserAfterActivationInEnglish() { + testRenderer.setLocale("en"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: no", + "Active: yes", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterActivationInGerman() { + testRenderer.setLocale("de"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: nein", + "Aktiv: ja", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulActivationTest { + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + + @Nested + class ForExternalUserTest { + + @BeforeEach + void mockExternalUser() { + User user = new User(); + user.setExternal(true); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldFailWithEnglishMsgIfUserIsExternal() { + testRenderer.setLocale("en"); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("External users cannot be activated"); + } + + @Test + void shouldFailWithGermanMsgIfUserIsExternal() { + testRenderer.setLocale("de"); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Externe Benutzer können nicht aktiviert werden"); + } + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToExternalCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToExternalCommandTest.java new file mode 100644 index 0000000000..571ee086d2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToExternalCommandTest.java @@ -0,0 +1,155 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserConvertToExternalCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserConvertToExternalCommand command; + + @BeforeEach + void initCommand() { + command = new UserConvertToExternalCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulConversionTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(false); + user.setActive(true); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldConvertToExternalUser() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isTrue(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldPrintUserAfterConversionInEnglish() { + testRenderer.setLocale("en"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: yes", + "Active: yes", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterConversionInGerman() { + testRenderer.setLocale("de"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: ja", + "Aktiv: ja", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulConversionTest { + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToInternalCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToInternalCommandTest.java new file mode 100644 index 0000000000..f7309754ba --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserConvertToInternalCommandTest.java @@ -0,0 +1,157 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserConvertToInternalCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserConvertToInternalCommand command; + + @BeforeEach + void initCommand() { + command = new UserConvertToInternalCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulActivationTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(true); + user.setActive(true); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldActivateInternalUser() { + command.setPassword("havelock123"); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("havelock123"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldPrintUserAfterActivationInEnglish() { + testRenderer.setLocale("en"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: no", + "Active: yes", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterActivationInGerman() { + testRenderer.setLocale("de"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: nein", + "Aktiv: ja", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulConversionTest { + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserCreateCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserCreateCommandTest.java new file mode 100644 index 0000000000..42ac25b8f2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserCreateCommandTest.java @@ -0,0 +1,237 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.cli.CommandValidator; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserCreateCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private CommandValidator validator; + @Mock + private UserManager manager; + + private UserCreateCommand command; + + @BeforeEach + void initCommand() { + command = new UserCreateCommand(testRenderer.getTemplateRenderer(), validator, manager); + } + + @Nested + class ForSuccessfulCreationTest { + + @BeforeEach + void mockCreation() { + when(manager.create(any())) + .thenAnswer(invocation -> { + User createdUser = invocation.getArgument(0, User.class); + createdUser.setCreationDate(1649262000000L); + return createdUser; + }); + + command.setUsername("havelock"); + command.setDisplayName("Havelock Vetinari"); + command.setEmail("havelock.vetinari@discworld"); + } + + @Test + void shouldCreateInternalUser() { + command.setPassword("patrician"); + + command.run(); + + verify(manager).create(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldCreateExternalUser() { + command.setExternal(true); + + command.run(); + + verify(manager).create(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isTrue(); + assertThat(argument.getPassword()).isNull(); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldCreateInactiveUser() { + command.setPassword("patrician"); + command.setInactive(true); + + command.run(); + + verify(manager).create(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isFalse(); + return true; + })); + } + + @Test + void shouldPrintUserAfterCreationInEnglish() { + testRenderer.setLocale("en"); + command.setPassword("patrician"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: no", + "Active: yes", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified:" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterCreationInGerman() { + testRenderer.setLocale("de"); + command.setPassword("patrician"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: nein", + "Aktiv: ja", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet:" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulCreationTest { + + @Test + void shouldFailIfValidatorFails() { + doThrow(picocli.CommandLine.ParameterException.class).when(validator).validate(); + + assertThrows( + picocli.CommandLine.ParameterException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()).isEmpty(); + } + + @Test + void shouldFailWithEnglishMsgIfInternalUserWithoutPassword() { + testRenderer.setLocale("en"); + + assertThrows(CliExitException.class, () -> command.run()); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Password is required for internal users"); + } + + @Test + void shouldFailWithGermanMsgIfInternalUserWithoutPassword() { + testRenderer.setLocale("de"); + + assertThrows(CliExitException.class, () -> command.run()); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Für interne Benutzer muss ein Passwort gesetzt werden"); + } + + @Test + void shouldFailWithEnglishMsgIfExternalUserAndInactive() { + testRenderer.setLocale("en"); + command.setExternal(true); + command.setInactive(true); + + assertThrows(CliExitException.class, () -> command.run()); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("External users cannot be deactivated"); + } + + @Test + void shouldFailWithGermanMsgIfExternalUserWithInactive() { + testRenderer.setLocale("de"); + command.setExternal(true); + command.setInactive(true); + + assertThrows(CliExitException.class, () -> command.run()); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Externe Benutzer können nicht deaktiviert werden"); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeactivateCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeactivateCommandTest.java new file mode 100644 index 0000000000..2d0cf339ec --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeactivateCommandTest.java @@ -0,0 +1,188 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserDeactivateCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserDeactivateCommand command; + + @BeforeEach + void initCommand() { + command = new UserDeactivateCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulDeactivationTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(false); + user.setActive(true); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldDeactivateInternalUser() { + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isFalse(); + return true; + })); + } + + @Test + void shouldPrintUserAfterDeactivationInEnglish() { + testRenderer.setLocale("en"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: no", + "Active: no", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterDeactivationInGerman() { + testRenderer.setLocale("de"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: nein", + "Aktiv: nein", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulActivationTest { + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + + @Nested + class ForExternalUserTest { + + @BeforeEach + void mockExternalUser() { + User user = new User(); + user.setExternal(true); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldFailWithEnglishMsgIfUserIsExternal() { + testRenderer.setLocale("en"); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("External users cannot be deactivated"); + } + + @Test + void shouldFailWithGermanMsgIfUserIsExternal() { + testRenderer.setLocale("de"); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Externe Benutzer können nicht deaktiviert werden"); + } + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeleteCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeleteCommandTest.java new file mode 100644 index 0000000000..af2c172c28 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserDeleteCommandTest.java @@ -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.user.cli; + +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.user.User; +import sonia.scm.user.UserManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserDeleteCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserDeleteCommand command; + + @BeforeEach + void initCommand() { + command = new UserDeleteCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulDeletionTest { + + private User user; + + @BeforeEach + void mockGet() { + user = new User(); + lenient().when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldRenderPromptInEnglishWithoutYesFlag() { + testRenderer.setLocale("en"); + + command.run(); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("If you really want to delete this user please pass --yes"); + } + + @Test + void shouldRenderPromptInGermanWithoutYesFlag() { + testRenderer.setLocale("de"); + + command.run(); + + verifyNoInteractions(manager); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Wenn dieser Benutzer endgültig gelöscht werden soll, setzen Sie bitte --yes"); + } + + @Test + void shouldDeleteUserWithEnglishMsg() { + testRenderer.setLocale("en"); + command.setShouldDelete(true); + + command.run(); + + verify(manager).delete(user); + assertThat(testRenderer.getStdOut()).contains("Successfully deleted user"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldDeleteUserWithGermanMsg() { + testRenderer.setLocale("de"); + command.setShouldDelete(true); + + command.run(); + + verify(manager).delete(user); + assertThat(testRenderer.getStdOut()).contains("Benutzer erfolgreich gelöscht"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulDeletionTest { + + @Test + void shouldFailSilentlyIfUserNotFound() { + when(manager.get(any())).thenReturn(null); + command.setShouldDelete(true); + + command.run(); + + verify(manager, never()).delete(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserGetCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserGetCommandTest.java new file mode 100644 index 0000000000..0e8896182b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserGetCommandTest.java @@ -0,0 +1,136 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +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.when; + +@ExtendWith(MockitoExtension.class) +class UserGetCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + + private UserGetCommand command; + + @BeforeEach + void initCommand() { + command = new UserGetCommand(testRenderer.getTemplateRenderer(), manager); + } + + @Nested + class ForSuccessfulGetTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(true); + user.setActive(false); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldPrintUserInEnglish() { + testRenderer.setLocale("en"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Havelock Vetinari", + "Email address: havelock.vetinari@discworld", + "External: yes", + "Active: no", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserInGerman() { + testRenderer.setLocale("de"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Havelock Vetinari ", + "E-Mail-Adresse: havelock.vetinari@discworld", + "Extern: ja", + "Aktiv: nein", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()) + .isEmpty(); + } + } + + @Nested + class ForUnsuccessfulGetTest { + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserListCommandTest.java new file mode 100644 index 0000000000..ed0a315d54 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserListCommandTest.java @@ -0,0 +1,120 @@ +/* + * 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.user.cli; + +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.cli.TemplateTestRenderer; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserListCommandTest { + + @Mock + private UserManager manager; + + private final UserCommandBeanMapper beanMapper = new UserCommandBeanMapperImpl(); + private final TemplateTestRenderer testRenderer = new TemplateTestRenderer(); + + private UserListCommand command; + + @BeforeEach + void initCommand() { + command = new UserListCommand(testRenderer.createTemplateRenderer(), manager, beanMapper); + } + + @BeforeEach + void mockGroups() { + User internalUser = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + User externalUser = new User("mustrum", "Mustrum Ridcully", "mustrum.ridcully@discworld"); + externalUser.setExternal(true); + externalUser.setActive(false); + when(manager.getAll()) + .thenReturn( + asList( + internalUser, + externalUser)); + } + + @Test + void shouldRenderShortTableInEnglish() { + testRenderer.setLocale("en"); + command.setSpec(testRenderer.getMockedSpeck()); + command.setUseShortTemplate(true); + + command.run(); + + assertThat(testRenderer.getStdOut()).isEqualTo("havelock\nmustrum\n"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldRenderShortTableInGerman() { + testRenderer.setLocale("de"); + command.setSpec(testRenderer.getMockedSpeck()); + command.setUseShortTemplate(true); + + command.run(); + + assertThat(testRenderer.getStdOut()).isEqualTo("havelock\nmustrum\n"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldRenderLongTableInEnglish() { + testRenderer.setLocale("en"); + command.setSpec(testRenderer.getMockedSpeck()); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .isEqualTo("USERNAME DISPLAY NAME EMAIL ADDRESS EXTERNAL ACTIVE\n" + + "havelock Havelock Vetinari havelock.vetinari@discworld no yes \n" + + "mustrum Mustrum Ridcully mustrum.ridcully@discworld yes no \n"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldRenderLongTableInGerman() { + testRenderer.setLocale("de"); + command.setSpec(testRenderer.getMockedSpeck()); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .isEqualTo("BENUTZERNAME ANZEIGENAME E-MAIL-ADRESSE EXTERN AKTIV\n" + + "havelock Havelock Vetinari havelock.vetinari@discworld nein ja \n" + + "mustrum Mustrum Ridcully mustrum.ridcully@discworld ja nein \n"); + assertThat(testRenderer.getStdErr()).isEmpty(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserModifyCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserModifyCommandTest.java new file mode 100644 index 0000000000..39eb883c7a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserModifyCommandTest.java @@ -0,0 +1,233 @@ +/* + * 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.user.cli; + +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.cli.CliExitException; +import sonia.scm.cli.CommandValidator; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserModifyCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private CommandValidator validator; + @Mock + private UserManager manager; + + private UserModifyCommand command; + + @BeforeEach + void initCommand() { + command = new UserModifyCommand(testRenderer.getTemplateRenderer(), validator, manager); + } + + @Nested + class ForSuccessfulModificationTest { + + @BeforeEach + void mockGet() { + User user = new User("havelock", "Havelock Vetinari", "havelock.vetinari@discworld"); + user.setPassword("patrician"); + user.setExternal(false); + user.setActive(true); + user.setCreationDate(1649262000000L); + user.setLastModified(1649272000000L); + when(manager.get(any())).thenReturn(user); + } + + @Test + void shouldModifyUser() { + command.setDisplayName("Lord Vetinari"); + command.setEmail("patrician@discworld"); + command.setPassword("havelock"); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Lord Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("havelock"); + assertThat(argument.getMail()).isEqualTo("patrician@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldNotModifyDisplayNameIfNotSet() { + command.setEmail("patrician@discworld"); + command.setPassword("havelock"); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Havelock Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("havelock"); + assertThat(argument.getMail()).isEqualTo("patrician@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldNotModifyEmailIfNotSet() { + command.setDisplayName("Lord Vetinari"); + command.setPassword("havelock"); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Lord Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("havelock"); + assertThat(argument.getMail()).isEqualTo("havelock.vetinari@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldNotModifyPasswordIfNotSet() { + command.setEmail("patrician@discworld"); + command.setDisplayName("Lord Vetinari"); + + command.run(); + + verify(manager).modify(argThat(argument -> { + assertThat(argument.getName()).isEqualTo("havelock"); + assertThat(argument.getDisplayName()).isEqualTo("Lord Vetinari"); + assertThat(argument.isExternal()).isFalse(); + assertThat(argument.getPassword()).isEqualTo("patrician"); + assertThat(argument.getMail()).isEqualTo("patrician@discworld"); + assertThat(argument.isActive()).isTrue(); + return true; + })); + } + + @Test + void shouldPrintUserAfterModificationInEnglish() { + testRenderer.setLocale("en"); + command.setDisplayName("Lord Vetinari"); + command.setEmail("patrician@discworld"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Username: havelock", + "Display Name: Lord Vetinari", + "Email address: patrician@discworld", + "External: no", + "Active: yes", + "Creation Date: 2022-04-06T16:20:00Z", + "Last Modified: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + + @Test + void shouldPrintUserAfterModificationInGerman() { + testRenderer.setLocale("de"); + command.setDisplayName("Lord Vetinari"); + command.setEmail("patrician@discworld"); + + command.run(); + + assertThat(testRenderer.getStdOut()) + .contains( + "Benutzername: havelock", + "Anzeigename: Lord Vetinari ", + "E-Mail-Adresse: patrician@discworld", + "Extern: nein", + "Aktiv: ja", + "Erstellt: 2022-04-06T16:20:00Z", + "Zuletzt bearbeitet: 2022-04-06T19:06:40Z" + ); + assertThat(testRenderer.getStdErr()).isEmpty(); + } + } + + @Nested + class ForUnsuccessfulModificationTest { + + @Test + void shouldFailIfValidatorFails() { + doThrow(picocli.CommandLine.ParameterException.class).when(validator).validate(); + + assertThrows( + picocli.CommandLine.ParameterException.class, + () -> command.run() + ); + + assertThat(testRenderer.getStdOut()).isEmpty(); + } + + @Test + void shouldFailWithEnglishMsgIfUserNotFound() { + testRenderer.setLocale("en"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldFailWithGermanMsgIfUserNotFound() { + testRenderer.setLocale("de"); + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + verify(manager, never()).modify(any()); + assertThat(testRenderer.getStdOut()).isEmpty(); + assertThat(testRenderer.getStdErr()).contains("Benutzer konnte nicht gefunden werden"); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserTemplateTestRenderer.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserTemplateTestRenderer.java new file mode 100644 index 0000000000..de8c8fe877 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserTemplateTestRenderer.java @@ -0,0 +1,56 @@ +/* + * 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.user.cli; + +import sonia.scm.cli.TemplateTestRenderer; + +import java.util.ResourceBundle; + +class UserTemplateTestRenderer { + private final TemplateTestRenderer testRenderer = new TemplateTestRenderer(); + private final UserCommandBeanMapper beanMapper = new UserCommandBeanMapperImpl(); + private final UserTemplateRenderer templateRenderer = new UserTemplateRenderer(testRenderer.getContextMock(), testRenderer.getTemplateEngineFactory(), beanMapper) { + @Override + protected ResourceBundle getBundle() { + return testRenderer.getResourceBundle(); + } + }; + + UserTemplateRenderer getTemplateRenderer() { + return templateRenderer; + } + + String getStdOut() { + return testRenderer.getStdOut(); + } + + String getStdErr() { + return testRenderer.getStdErr(); + } + + public void setLocale(String locale) { + testRenderer.setLocale(locale); + } +}