diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..927a646 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# File generated by jbang setup@jabrena + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.html] +indent_style = space +indent_size = 4 + +[*.json] +indent_style = space +indent_size = 4 + +[*.xml] +indent_style = space +indent_size = 4 + +[*.java] +indent_style = space +indent_size = 4 + +[*.yml,*.yaml] +indent_style = space +indent_size = 2 + +[*.dsl] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.github/workflows/build_standard.yml b/.github/workflows/build_standard.yml index d3e6366..941ea35 100644 --- a/.github/workflows/build_standard.yml +++ b/.github/workflows/build_standard.yml @@ -11,13 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: 17 - name: Install Graphviz run: sudo apt-get -y install graphviz - name: Gradle caches @@ -44,7 +45,7 @@ jobs: path: ./out if-no-files-found: error - validate_java_11: + validate_java_17: runs-on: ubuntu-latest needs: - build @@ -56,14 +57,15 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: 17 - name: Execute to check if Java version is compatible run: ./out/bin/cm - validate_java_17: + validate_java_21: runs-on: ubuntu-latest needs: - build @@ -75,14 +77,15 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 17 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - java-version: 17 + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '21' - name: Execute to check if Java version is compatible run: ./out/bin/cm - validate_java_21: + validate_java_24: runs-on: ubuntu-latest needs: - build @@ -94,9 +97,10 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 21 - uses: actions/setup-java@v1 + - name: Set up JDK 24 + uses: actions/setup-java@v4 with: - java-version: 21 + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '24' - name: Execute to check if Java version is compatible run: ./out/bin/cm diff --git a/.gitignore b/.gitignore index c715fa6..b9d44e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,4 @@ -# Compiled class file -*.class -**/target -**/out - -# Build -/build +build/ .gradle/ - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# IntelliJ -/.idea +.cursor/ +.DS_Store diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..e45f53e --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1 @@ +java=17.0.9-graalce diff --git a/README.md b/README.md index 3aaaae2..42bd133 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,14 @@ The following examples illustrate the CLI usage. If you want to contribute to this project you can create a fork and a pull request. The project is built with Gradle, so you can import it as Gradle project within Eclipse or IntelliJ IDEA (or any other IDE supporting Gradle). ```bash +# Use sdkman or install manually JDK 17 +sdk env install + ./gradlew clean build +./gradlew clean build jacocoTestReport +jwebserver -p 9000 -d "$(pwd)/build/reports/" +./gradlew clean build snapshot +java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar ``` ## Contributing @@ -100,4 +107,3 @@ Contribution is always welcome! Here are some ways how you can contribute: ## Licence ContextMapper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). - diff --git a/build.gradle b/build.gradle index 7254522..6611a84 100644 --- a/build.gradle +++ b/build.gradle @@ -7,34 +7,38 @@ plugins { id 'nebula.release' version '19.0.10' } -group 'org.contextmapper' +group = 'org.contextmapper' -sourceCompatibility = '11' -targetCompatibility = '11' +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} repositories { mavenCentral() } dependencies { - implementation "commons-cli:commons-cli:${commonsCliVersion}" implementation "org.contextmapper:context-mapper-dsl:${cmlVersion}" + implementation "info.picocli:picocli:${picocliVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}" testImplementation "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}" testImplementation "org.assertj:assertj-core:${assertJVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.mockito:mockito-junit-jupiter:${mockitoVersion}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}" } application { - mainClassName = 'org.contextmapper.cli.ContextMapperCLI' - applicationName = 'context-mapper-cli' -} -startScripts { + mainClass = 'org.contextmapper.cli.ContextMapperCLI' applicationName = 'cm' } + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') + failOnError false +} + jar { manifest { attributes ( @@ -43,6 +47,12 @@ jar { 'Main-Class': 'org.contextmapper.cli.ContextMapperCLI' ) } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude('META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/DEPENDENCIES') + exclude('META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/services/**') } if (!project.hasProperty('signing.secretKeyRingFile')) { @@ -50,14 +60,12 @@ if (!project.hasProperty('signing.secretKeyRingFile')) { } test { - useJUnitPlatform() - testLogging { - showExceptions true - exceptionFormat "full" + showExceptions = true + exceptionFormat = "full" - showCauses true - showStackTraces true + showCauses = true + showStackTraces = true } } @@ -146,14 +154,22 @@ signing { sign(publishing.publications) } +testing { + suites { + test(JvmTestSuite) { + useJUnitJupiter(jUnitVersion) + } + } +} + tasks.withType(GenerateModuleMetadata) { enabled = false } -tasks.withType(CreateStartScripts).each { task -> - task.doLast { - String text = task.windowsScript.text +tasks.withType(CreateStartScripts).configureEach { + doLast { + String text = windowsScript.text text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" }) - task.windowsScript.write text + windowsScript.text = text } } diff --git a/context_mapper_cli.feature b/context_mapper_cli.feature new file mode 100644 index 0000000..586eb84 --- /dev/null +++ b/context_mapper_cli.feature @@ -0,0 +1,65 @@ +Feature: Context Mapper CLI Usage + As a user of the Context Mapper CLI + I want to be able to validate CML files and generate various outputs + So that I can effectively use the tool for my Domain-Driven Design modeling. + + Background: + Given the Context Mapper CLI is installed + + Scenario: Display help for the validate command + When I run the command `./cm validate -h` + Then the output should contain: + """ + Context Mapper CLI + usage: cm validate + -h,--help Prints this message. + -i,--input Path to the CML file which you want to validate. + """ + + Scenario: Validate a CML file + Given a CML file named "DDD-Sample.cml" exists + When I run the command `./cm validate -i DDD-Sample.cml` + Then the CLI should validate "DDD-Sample.cml" successfully + + Scenario: Display help for the generate command + When I run the command `./cm generate -h` + Then the output should contain: + """ + Context Mapper CLI + usage: cm generate + -f,--outputFile The name of the file that shall be generated + (only used by Freemarker generator, as we cannot + know the file extension). + -g,--generator The generator you want to call. Use one of the + following values: context-map (Graphical DDD + Context Map), plantuml (PlantUML class-, + component-, and state diagrams.), generic + (Generate generic text with Freemarker template) + -h,--help Prints this message. + -i,--input Path to the CML file for which you want to + generate output. + -o,--outputDir Path to the directory into which you want to + generate. + -t,--template Path to the Freemarker template you want to use. + This parameter is only used if you pass 'generic' + to the 'generator' (-g) parameter. + """ + + Scenario: Generate PlantUML output + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + When I run the command `./cm generate -i DDD-Sample.cml -g plantuml -o ./output-directory` + Then PlantUML diagrams should be generated in "./output-directory" from "DDD-Sample.cml" + + Scenario: Generate Context Map output + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + When I run the command `./cm generate -i DDD-Sample.cml -g context-map -o ./output-directory` + Then a Context Map should be generated in "./output-directory" from "DDD-Sample.cml" + + Scenario: Generate arbitrary text file with Freemarker template + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + And a Freemarker template file named "template.md.ftl" + When I run the command `./cm generate -i DDD-Sample.cml -g generic -o ./output-directory -t template.md.ftl -f glossary.md` + Then a file named "glossary.md" should be generated in "./output-directory" using "template.md.ftl" and "DDD-Sample.cml" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fc6a80f..f0f8992 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,10 +3,9 @@ ossSnapshotRepository=https://oss.sonatype.org/content/repositories/snapshots/ ossReleaseStagingRepository=https://oss.sonatype.org/service/local/staging/deploy/maven2/ # dependency versions -jUnitVersion=5.9.1 -assertJVersion=3.19.0 -mockitoVersion=3.9.0 +jUnitVersion=5.12.2 +assertJVersion=3.27.3 +mockitoVersion=5.17.0 -commonsCliVersion=1.4 cmlVersion=6.12.0 - +picocliVersion=4.7.7 diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index de72e04..d297fb6 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -1,75 +1,31 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli; -import org.contextmapper.cli.commands.CliCommand; -import org.contextmapper.cli.commands.GenerateCommand; -import org.contextmapper.cli.commands.ValidateCommand; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class ContextMapperCLI { - - private static final int REQUIRED_JAVA_VERSION = 11; - private static final String VALIDATE_COMMAND = "validate"; - private static final String GENERATE_COMMAND = "generate"; - - private CliCommand generateCommand; - private CliCommand validateCommand; - - public ContextMapperCLI() { - this.generateCommand = new GenerateCommand(); - this.validateCommand = new ValidateCommand(); - } - - public static void main(String[] args) { - int javaVersion = Runtime.version().feature(); - - if (Runtime.version().feature() >= REQUIRED_JAVA_VERSION) { - new ContextMapperCLI().run(args); - } else { - System.out.printf("Invalid Java version '%s' (>=%s is required).", javaVersion, REQUIRED_JAVA_VERSION); - System.exit(1); - } +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Command( + name = "cm", + versionProvider = VersionProvider.class, + description = "Context Mapper CLI", + subcommands = { + ValidateCommand.class, + GenerateCommand.class + }, + mixinStandardHelpOptions = true, + usageHelpAutoWidth = true) +public class ContextMapperCLI implements Runnable { + + @Override + public void run() { + System.out.println("Context Mapper CLI. Use 'cm --help' for usage information."); } - protected void run(String[] args) { - System.out.println("Context Mapper CLI " + getVersion()); - - if (args == null || args.length == 0) { - printUsages(); - } else if (VALIDATE_COMMAND.equalsIgnoreCase(args[0])) { - validateCommand.run(Arrays.copyOfRange(args, 1, args.length)); - } else if (GENERATE_COMMAND.equalsIgnoreCase(args[0])) { - generateCommand.run(Arrays.copyOfRange(args, 1, args.length)); - } else { - System.out.println("Invalid input"); - System.exit(127); - } + public static int runCLI(String[] args) { + return new CommandLine(new ContextMapperCLI()).execute(args); } - private void printUsages() { - System.out.println("Usage: cm " + VALIDATE_COMMAND + "|" + GENERATE_COMMAND + " [options]"); - } - - private String getVersion() { - String implVersion = ContextMapperCLI.class.getPackage().getImplementationVersion(); - return implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION"; + public static void main(String[] args) { + int exitCode = runCLI(args); + System.exit(exitCode); } - } diff --git a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java b/src/main/java/org/contextmapper/cli/ContextMapperGenerator.java similarity index 55% rename from src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java rename to src/main/java/org/contextmapper/cli/ContextMapperGenerator.java index 3f87533..15e42e9 100644 --- a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperGenerator.java @@ -1,4 +1,8 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; import org.contextmapper.dsl.generator.ContextMapGenerator; import org.contextmapper.dsl.generator.GenericContentGenerator; @@ -27,29 +31,33 @@ public String getDescription() { return description; } + public String getDisplayName() { + return this.name + " (" + this.description + ")"; + } + @Override public String toString() { - return this.name + " (" + this.description + ")"; + return this.name; } public static ContextMapperGenerator byName(String name) { - if (name == null || "".equals(name)) + // Preconditions check + if (Objects.isNull(name) || name.trim().isEmpty()) { throw new IllegalArgumentException("Please provide a name for the generator."); - - for (ContextMapperGenerator generator : values()) { - if (generator.getName().equals(name)) - return generator; } - - throw new IllegalArgumentException("No generator found for the name '" + name + "'."); + return Arrays.stream(values()) + .filter(gen -> gen.getName().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No generator found for the name '" + name + + "'. Valid values are: " + + Arrays.stream(values()).map(ContextMapperGenerator::getName).collect(Collectors.joining(", ")))); } public IGenerator2 getGenerator() { - if (this == CONTEXT_MAP) - return new ContextMapGenerator(); - if (this == PLANT_UML) - return new PlantUMLGenerator(); - return new GenericContentGenerator(); + return switch (this) { + case CONTEXT_MAP -> new ContextMapGenerator(); + case PLANT_UML -> new PlantUMLGenerator(); + case GENERIC -> new GenericContentGenerator(); + }; } - } diff --git a/src/main/java/org/contextmapper/cli/GenerateCommand.java b/src/main/java/org/contextmapper/cli/GenerateCommand.java new file mode 100644 index 0000000..fbb3405 --- /dev/null +++ b/src/main/java/org/contextmapper/cli/GenerateCommand.java @@ -0,0 +1,131 @@ +package org.contextmapper.cli; + +import java.io.File; +import java.util.Objects; +import java.util.concurrent.Callable; + +import org.contextmapper.dsl.cml.CMLResource; +import org.contextmapper.dsl.generator.GenericContentGenerator; +import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; +import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; +import org.eclipse.xtext.generator.IGenerator2; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command( + name = "generate", + description = "Generates output from a CML file.", + mixinStandardHelpOptions = true) +public class GenerateCommand implements Callable { + + @Option( + names = {"-i", "--input"}, + description = "Path to the CML file for which you want to generate output.", + required = true) + private String inputPath; + + @Option( + names = {"-g", "--generator"}, + description = """ + The generator you want to call. + Use one of the following values: ${COMPLETION-CANDIDATES} + """) + private ContextMapperGenerator generatorType; + + @Option( + names = {"-o", "--outputDir"}, + description = "Path to the directory into which you want to generate.", + defaultValue = ".") + private String outputDir; + + @Option( + names = {"-t", "--template"}, + description = """ + Path to the Freemarker template you want to use. + This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter. + """ + ) + private File templateFile; + + @Option( + names = {"-f", "--outputFile"}, + description = """ + The name of the file that shall be generated (only used by Freemarker generator, + as we cannot know the file extension). + """ + ) + private String outputFileName; + + private boolean isInputFileValid(String path) { + File inputFile = new File(path); + if (!inputFile.exists()) { + System.err.println("ERROR: The file '" + path + "' does not exist."); + return false; + } + if (!path.endsWith(".cml")) { + System.err.println("ERROR: Please provide a path to a CML (*.cml) file."); + return false; + } + return true; + } + + private boolean doesOutputDirExist(String dirPath) { + if (Objects.isNull(dirPath) || dirPath.trim().isEmpty()) { + // Should not happen with defaultValue, but good for robustness + System.err.println("ERROR: Output directory path is empty."); + return false; + } + + File dir = new File(dirPath); + if (!dir.exists()) { + System.err.println("ERROR: Output directory '" + dirPath + "' does not exist."); + return false; + } + if (!dir.isDirectory()) { + System.err.println("ERROR: '" + dirPath + "' is not a directory."); + return false; + } + return true; + } + + private IGenerator2 getGenerator() { + IGenerator2 selectedGenerator = generatorType.getGenerator(); + if (selectedGenerator instanceof GenericContentGenerator) { + if (Objects.isNull(templateFile)) { + throw new IllegalArgumentException("The --template (-t) parameter is required for the 'generic' generator."); + } + if (Objects.isNull(outputFileName) || outputFileName.trim().isEmpty()) { + throw new IllegalArgumentException("The --outputFile (-f) parameter is required for the 'generic' generator."); + } + final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) selectedGenerator; + genericContentGenerator.setFreemarkerTemplateFile(templateFile); + genericContentGenerator.setTargetFileName(outputFileName); + return genericContentGenerator; + } + return selectedGenerator; + } + + private Integer runCall() { + // Preconditions check + if (!isInputFileValid(inputPath)) { + return 1; + } + if (!doesOutputDirExist(this.outputDir)) { + return 1; + } + + StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); + CMLResource cmlResource = cmAPI.loadCML(inputPath); + IGenerator2 generator = getGenerator(); + + cmAPI.callGenerator(cmlResource, generator, this.outputDir); + System.out.println("Generated into '" + this.outputDir + "'."); + return 0; + } + + @Override + public Integer call() throws Exception { + return runCall(); + } +} diff --git a/src/main/java/org/contextmapper/cli/ValidateCommand.java b/src/main/java/org/contextmapper/cli/ValidateCommand.java new file mode 100644 index 0000000..420a30a --- /dev/null +++ b/src/main/java/org/contextmapper/cli/ValidateCommand.java @@ -0,0 +1,60 @@ +package org.contextmapper.cli; + +import java.io.File; +import java.util.concurrent.Callable; + +import org.contextmapper.dsl.cml.CMLResource; +import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; +import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; +import org.eclipse.emf.ecore.resource.Resource.Diagnostic; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command(name = "validate", description = "Validates a CML file.", mixinStandardHelpOptions = true) +public class ValidateCommand implements Callable { + + @Option(names = {"-i", "--input"}, description = "Path to the CML file which you want to validate.", required = true) + private String inputPath; + + @Override + public Integer call() throws Exception { + if (!isInputFileValid(inputPath)) { + return 1; + } + + StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); + CMLResource cmlResource = cmAPI.loadCML(inputPath); + + printValidationMessages(cmlResource, inputPath); + + return cmlResource.getErrors().isEmpty() ? 0 : 1; + } + + protected boolean isInputFileValid(String path) { + File inputFile = new File(path); + if (!inputFile.exists()) { + System.err.println("ERROR: The file '" + path + "' does not exist."); + return false; + } + if (!path.endsWith(".cml")) { + System.err.println("ERROR: Please provide a path to a CML (*.cml) file."); + return false; + } + return true; + } + + protected void printValidationMessages(final CMLResource cmlResource, final String filePath) { + if (cmlResource.getErrors().isEmpty()) { + System.out.println("The CML file '" + filePath + "' has been validated without errors."); + } else { + for (Diagnostic diagnostic : cmlResource.getErrors()) { + System.err.println("ERROR in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" + diagnostic.getMessage()); + } + } + + for (Diagnostic diagnostic : cmlResource.getWarnings()) { + System.out.println("WARNING in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" + diagnostic.getMessage()); + } + } +} diff --git a/src/main/java/org/contextmapper/cli/VersionProvider.java b/src/main/java/org/contextmapper/cli/VersionProvider.java new file mode 100644 index 0000000..ed6b78c --- /dev/null +++ b/src/main/java/org/contextmapper/cli/VersionProvider.java @@ -0,0 +1,23 @@ +package org.contextmapper.cli; + +import java.util.Optional; + +import picocli.CommandLine.IVersionProvider; + +class VersionProvider implements IVersionProvider { + + @Override + public String[] getVersion() throws Exception { + Package programPackage = getPackageToInspect(); + String versionString = Optional.ofNullable(programPackage) + .map(Package::getImplementationVersion) + .map(v -> "v" + v) + .orElse("DEVELOPMENT VERSION"); + return new String[]{"Context Mapper CLI " + versionString}; + } + + //Refactored to help the testing process + protected Package getPackageToInspect() { + return ContextMapperCLI.class.getPackage(); + } +} diff --git a/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java b/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java deleted file mode 100644 index c9515d4..0000000 --- a/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.contextmapper.cli.commands; - -import java.io.File; - -public abstract class AbstractCliCommand implements CliCommand { - - protected boolean isInputFileValid(String inputPath) { - File inputFile = new File(inputPath); - if (!inputFile.exists()) { - System.out.println("ERROR: The file '" + inputPath + "' does not exist."); - return false; - } - if (!inputPath.endsWith(".cml")) { - System.out.println("ERROR: Please provide a path to a CML (*.cml) file."); - return false; - } - return true; - } - -} diff --git a/src/main/java/org/contextmapper/cli/commands/CliCommand.java b/src/main/java/org/contextmapper/cli/commands/CliCommand.java deleted file mode 100644 index 2bce4b1..0000000 --- a/src/main/java/org/contextmapper/cli/commands/CliCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -public interface CliCommand { - - /** - * Runs a CLI command. - * - * @param args the arguments passed in addition to the command - */ - void run(String[] args); - -} diff --git a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java deleted file mode 100644 index 6a9ffe6..0000000 --- a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -import org.apache.commons.cli.*; -import org.contextmapper.dsl.cml.CMLResource; -import org.contextmapper.dsl.generator.GenericContentGenerator; -import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; -import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; -import org.eclipse.xtext.generator.IGenerator2; - -import java.io.File; -import java.util.Arrays; -import java.util.stream.Collectors; - -public class GenerateCommand extends AbstractCliCommand { - - private String outputDir = "./"; - - @Override - public void run(String[] args) { - Options options = createOptions(); - - CommandLineParser commandLineParser = new DefaultParser(); - - try { - CommandLine cmd = commandLineParser.parse(options, args); - - if (cmd.hasOption("help") || cmd.hasOption("h")) { - printHelp(options); - } else { - String inputPath = cmd.getOptionValue("input").trim(); - if (!isInputFileValid(inputPath)) - return; - - if (cmd.hasOption("outputDir")) - this.outputDir = cmd.getOptionValue("outputDir"); - - if (doesOutputDirExist(this.outputDir)) { - StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); - CMLResource cmlResource = cmAPI.loadCML(inputPath); - cmAPI.callGenerator(cmlResource, getGenerator(cmd), this.outputDir); - System.out.println("Generated into '" + this.outputDir + "'."); - } - } - } catch (ParseException e) { - printHelp(options); - } - } - - private IGenerator2 getGenerator(CommandLine cmd) { - final ContextMapperGenerator generator = ContextMapperGenerator.byName(cmd.getOptionValue("generator")); - if (generator.getGenerator() instanceof GenericContentGenerator) { - final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) generator.getGenerator(); - genericContentGenerator.setFreemarkerTemplateFile(new File(cmd.getOptionValue("template"))); - genericContentGenerator.setTargetFileName(cmd.getOptionValue("outputFile")); - return genericContentGenerator; - } - return generator.getGenerator(); - } - - private Options createOptions() { - Options options = new Options(); - - Option input = new Option("i", "input", true, "Path to the CML file for which you want to generate output."); - input.setRequired(true); - options.addOption(input); - - Option generator = new Option("g", "generator", true, - "The generator you want to call. Use one of the following values: " + - Arrays.stream(ContextMapperGenerator.values()).map(ContextMapperGenerator::toString).collect(Collectors.joining(", "))); - generator.setRequired(true); - options.addOption(generator); - - options.addOption(new Option("o", "outputDir", true, "Path to the directory into which you want to generate.")); - options.addOption(new Option("t", "template", true, - "Path to the Freemarker template you want to use. This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter.")); - options.addOption(new Option("f", "outputFile", true, - "The name of the file that shall be generated (only used by Freemarker generator, as we cannot know the file extension).")); - options.addOption(new Option("h", "help", false, "Prints this message.")); - - return options; - } - - protected void printHelp(final Options options) { - new HelpFormatter().printHelp("cm generate", options); - } - - private boolean doesOutputDirExist(String outputDir) { - if (outputDir == null || "".equals(outputDir)) { - System.out.println("ERROR: '" + outputDir + "' is not a directory."); - return false; - } - - File dir = new File(outputDir); - if (!dir.exists() || !dir.isDirectory()) { - System.out.println("ERROR: '" + outputDir + "' is not a directory."); - return false; - } - return true; - } - -} diff --git a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java b/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java deleted file mode 100644 index 082e2a5..0000000 --- a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -import org.apache.commons.cli.*; -import org.contextmapper.dsl.cml.CMLResource; -import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; -import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; -import org.eclipse.emf.ecore.resource.Resource.Diagnostic; - -public class ValidateCommand extends AbstractCliCommand { - - @Override - public void run(String[] args) { - Options options = createOptions(); - - CommandLineParser commandLineParser = new DefaultParser(); - - try { - CommandLine cmd = commandLineParser.parse(options, args); - - if (cmd.hasOption("help") || cmd.hasOption("h")) { - printHelp(options); - } else { - // check that input CML files exists - String inputPath = cmd.getOptionValue("input").trim(); - if (!isInputFileValid(inputPath)) - return; - - // load CML file - StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); - CMLResource cmlResource = cmAPI.loadCML(inputPath); - - // print validation errors/warnings - printValidationMessages(cmlResource, inputPath); - } - } catch (ParseException e) { - printHelp(options); - } - } - - private Options createOptions() { - Options options = new Options(); - - Option help = new Option("h", "help", false, "Prints this message."); - options.addOption(help); - - Option input = new Option("i", "input", true, "Path to the CML file which you want to validate."); - input.setRequired(true); - options.addOption(input); - - return options; - } - - protected void printValidationMessages(final CMLResource cmlResource, final String filePath) { - if (cmlResource.getErrors().isEmpty()) { - System.out.println("The CML file '" + filePath + "' has been validated without errors."); - } else { - for (Diagnostic diagnostic : cmlResource.getErrors()) { - System.out.println("ERROR in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" - + diagnostic.getMessage()); - } - } - - for (Diagnostic diagnostic : cmlResource.getWarnings()) { - System.out.println("WARNING in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" - + diagnostic.getMessage()); - } - } - - protected void printHelp(final Options options) { - new HelpFormatter().printHelp("cm validate", options); - } - -} diff --git a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java index e59d595..97b7e60 100644 --- a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java @@ -1,37 +1,15 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli; -import org.contextmapper.cli.commands.GenerateCommand; -import org.contextmapper.cli.commands.ValidateCommand; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.DisplayName; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -@ExtendWith(MockitoExtension.class) class ContextMapperCLITest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); @@ -39,99 +17,104 @@ class ContextMapperCLITest { private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; - @Mock - private ValidateCommand validateCommand; - - @Mock - private GenerateCommand generateCommand; - - @InjectMocks - private ContextMapperCLI contextMapperCLI; - @BeforeEach - public void setUpStreams() { + void setUp() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); } @AfterEach - public void restoreStreams() { + void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } @Test - void main_WhenCalledWithoutCommand_ThenPrintUsage() { - // given - final String[] params = new String[]{}; + @DisplayName("main() should print top-level help when called without command") + void main_WhenCalledWithoutCommand_ThenPrintsTopLevelHelp() { + // Given + // No arguments for this test case - // when - ContextMapperCLI.main(params); + // When + ContextMapperCLI.runCLI(new String[0]); - // then - assertThat(outContent.toString()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION" + System.lineSeparator() + - "Usage: cm validate|generate [options]" + System.lineSeparator()); + // Then + assertThat(outContent.toString()).contains("Context Mapper CLI. Use 'cm --help' for usage information."); } @Test - void run_WhenCalledWithoutCommand_ThenPrintUsage() { - // given - final String[] params = new String[]{}; - - // when - contextMapperCLI.run(params); - - // then - assertThat(outContent.toString()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION" + System.lineSeparator() + - "Usage: cm validate|generate [options]" + System.lineSeparator()); + @DisplayName("main() should print top-level help when called with --help option") + void main_WhenCalledWithHelpOption_ThenPrintsTopLevelHelp() { + // Given + String[] args = { "--help" }; + + // When + int exitCode = ContextMapperCLI.runCLI(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm [-hV] [COMMAND]") + .contains("Commands:") + .contains("validate Validates a CML file.") + .contains("generate Generates output from a CML file."); } @Test - void run_WhenCalledWithValidate_ThenCallValidateCommand() { - // given - final String[] params = new String[]{"validate"}; + @DisplayName("main() should print version when called with --version option") + void main_WhenCalledWithVersionOption_ThenPrintsVersion() { + // Given + String[] args = { "--version" }; - // when - contextMapperCLI.run(params); + // When + int exitCode = ContextMapperCLI.runCLI(args); - // then - verify(validateCommand).run(new String[]{}); + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString().trim()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION"); } @Test - void run_WhenCalledWithValidateAndAdditionalParams_ThenCallValidateCommandWithParams() { - // given - final String[] params = new String[]{"validate", "test-param"}; + @DisplayName("main() should print version when called with -V option") + void main_WhenCalledWithShortVersionOption_ThenPrintsVersion() { + // Given + String[] args = { "-V" }; - // when - contextMapperCLI.run(params); + // When + int exitCode = ContextMapperCLI.runCLI(args); - // then - verify(validateCommand).run(new String[]{"test-param"}); + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString().trim()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION"); } @Test - void run_WhenCalledWithGenerate_ThenCallGenerateCommand() { - // given - final String[] params = new String[]{"generate"}; - - // when - contextMapperCLI.run(params); - - // then - verify(generateCommand).run(new String[]{}); + @DisplayName("main() should print error and usage when called with an invalid option") + void main_WhenCalledWithInvalidOption_ThenPrintsErrorAndUsage() { + // Given + String[] args = { "--invalid-option" }; + + // When + int exitCode = ContextMapperCLI.runCLI(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Unknown option: '--invalid-option'") + .contains("Usage: cm [-hV] [COMMAND]"); } @Test - void run_WhenCalledWithGenerateAndAdditionalParams_ThenCallGenerateCommandWithParams() { - // given - final String[] params = new String[]{"generate", "plantuml"}; + @DisplayName("main() should print error and usage when called with an invalid subcommand") + void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { + // Given + String[] args = { "invalid-command" }; - // when - contextMapperCLI.run(params); + // When + int exitCode = ContextMapperCLI.runCLI(args); - // then - verify(generateCommand).run(new String[]{"plantuml"}); + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()).contains("Unmatched argument at index 0: 'invalid-command'"); } - } diff --git a/src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java new file mode 100644 index 0000000..bb19e32 --- /dev/null +++ b/src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java @@ -0,0 +1,138 @@ +package org.contextmapper.cli; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.eclipse.xtext.generator.IGenerator2; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class ContextMapperGeneratorTest { + + @ParameterizedTest + @CsvSource({ + "CONTEXT_MAP, context-map", + "PLANT_UML, plantuml", + "GENERIC, generic" + }) + void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(ContextMapperGenerator generator, + String expectedName) { + // Given + // generator and expectedName are provided by @CsvSource + + // When + final String name = generator.getName(); + + // Then + assertThat(name).isEqualTo(expectedName); + } + + @ParameterizedTest + @ValueSource(strings = { "CONTEXT_MAP", "PLANT_UML", "GENERIC" }) + void getDescription_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorDescription(final String enumValueAsString) { + // Given + final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); + + // When + final String description = generator.getDescription(); + + // Then + assertThat(description) + .isNotNull() + .isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "CONTEXT_MAP, context-map", + "PLANT_UML, plantuml", + "GENERIC, generic" + }) + void toString_ReturnsName(ContextMapperGenerator generator, String expectedName) { + // Given + // generator and expectedName are provided by @CsvSource + + // When + final String stringRepresentation = generator.toString(); + + // Then + assertThat(stringRepresentation).isEqualTo(expectedName); + } + + @ParameterizedTest + @ValueSource(strings = { "context-map", "plantuml", "generic", "CONTEXT-MAP", "PlantUML", "GeNeRiC" }) + void byName_WhenWithValidName_ThenReturnGenerator(final String validGeneratorKey) { + // Given + // validGeneratorKey is provided by @ValueSource + + // When + final ContextMapperGenerator generator = ContextMapperGenerator.byName(validGeneratorKey); + + // Then + assertThat(generator).isNotNull(); + } + + @Test + void byName_WhenWithoutName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> ContextMapperGenerator.byName(null)) + .withMessageContaining("Please provide a name for the generator."); + } + + @Test + void byName_WhenWithEmptyName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> ContextMapperGenerator.byName("")) + .withMessageContaining("Please provide a name for the generator."); + } + + @Test + void byName_WhenWithInvalidName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ContextMapperGenerator.byName("just a string")) + .withMessageContaining( + "No generator found for the name 'just a string'. Valid values are: context-map, plantuml, generic"); + } + + @ParameterizedTest + @ValueSource(strings = { "CONTEXT_MAP", "PLANT_UML", "GENERIC" }) + void getGenerator_WhenCalled_ThenReturnGeneratorImplementation(final String enumValueAsString) { + // Given + final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); + + // When + final IGenerator2 generatorImpl = generator.getGenerator(); + + // Then + assertThat(generatorImpl).isNotNull(); + } + + @ParameterizedTest + @CsvSource({ + "CONTEXT_MAP, context-map (Graphical DDD Context Map)", + "PLANT_UML, plantuml (PlantUML class-, component-, and state diagrams.)", + "GENERIC, generic (Generate generic text with Freemarker template)" + }) + void getDisplayName_ReturnsCorrectFormat(ContextMapperGenerator generator, String expectedDisplayName) { + // Given + // generator and expectedDisplayName are provided by @CsvSource + + // When + String displayName = generator.getDisplayName(); + + // Then + assertThat(displayName).contains(expectedDisplayName); + } +} diff --git a/src/test/java/org/contextmapper/cli/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/GenerateCommandTest.java new file mode 100644 index 0000000..18db18b --- /dev/null +++ b/src/test/java/org/contextmapper/cli/GenerateCommandTest.java @@ -0,0 +1,288 @@ +package org.contextmapper.cli; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +class GenerateCommandTest { + + @TempDir + Path testOutPath; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + + private CommandLine cmd; + private String testOutDirString; + + @BeforeEach + void setUp() throws IOException { + testOutDirString = testOutPath.toFile().getAbsolutePath(); + if (!Files.exists(testOutPath)) { + Files.createDirectories(testOutPath); + } + + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + cmd = new CommandLine(new ContextMapperCLI()); + } + + @AfterEach + void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + @Test + @DisplayName("run() should print help when called with -h option") + void run_WhenCalledWithHelp_ThenPrintHelp() { + // Given + String[] args = { "generate", "-h" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm generate [-hV] [-f=] [-g=]") + .contains("Generates output from a CML file."); + } + + @Test + @DisplayName("run() should print error when output directory does not exist") + void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { + // Given + String nonExistingDir = "/just-some-dir-that-hopefully-not-exists-unless-you-are-very-unlucky"; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", nonExistingDir }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Output directory '" + nonExistingDir + "' does not exist."); + } + + @Test + @DisplayName("run() should print error when output directory path is null") + void run_WhenOutputDirPathIsNull_ThenPrintError() { + // Given + // Picocli will prevent null values for options with default values if not explicitly set, + // so we test the method directly or simulate this scenario if GenerateCommand were used programmatically. + // Here, we simulate a direct call or an unlikely scenario where defaultValue isn't applied. + // For robust direct testing of doesOutputDirExist, one might instantiate GenerateCommand and call it. + // However, CLI execution path for a null default is tricky. This test highlights the logic. + GenerateCommand generateCommand = new GenerateCommand(); + // Simulate picocli not setting the default value or programmatic use + // For CLI, this path is less likely due to Picocli's handling of default values. + // We are testing the defensive check within doesOutputDirExist. + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml"}; // -o is omitted + + // When + // We can't directly pass null via CLI args for a field with a default. + // We will execute and check for expected picocli behavior or an error if our method was directly called with null. + // The current GenerateCommand uses "." as default. + // To test the null case in doesOutputDirExist, we would need to bypass picocli's default value mechanism. + // This test will instead verify that the default is used if -o is not provided. + // A dedicated test for doesOutputDirExist(null) would be more direct but separate from CLI flow. + + int exitCode = cmd.execute(args); // outputDir will default to "." + + // Then + assertThat(exitCode).isEqualTo(0); // Should succeed as "." is a valid default. + assertThat(outContent.toString()).contains("Generated into '.'"); + + // To truly test the null case of 'doesOutputDirExist' as per its internal logic, + // we'd call it directly. Picocli makes it hard to inject a null for an option with a default. + // Let's add a direct call for that specific logic branch: + GenerateCommand command = new GenerateCommand(); + // Inject dependencies or use reflection if needed to set outputDir to null, or make method public for testing + // For now, assuming we can call it: + // For a direct test of the method's null check: + // boolean result = command.doesOutputDirExist(null); // This would require refactoring or making method accessible + // assertThat(result).isFalse(); + // System.setErr(new PrintStream(errContent)); // Capture direct System.err output + // assertThat(errContent.toString()).contains("ERROR: Output directory path is empty."); // This is the expected message for null + } + + @Test + @DisplayName("run() should print error when output directory path is empty") + void run_WhenOutputDirPathIsEmpty_ThenPrintError() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "" }; + + // When + int exitCode = cmd.execute(args); + + // Then + // Picocli might handle empty string differently based on option config (allowEmpty = true/false) + // Assuming it passes the empty string to the command. + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Output directory path is empty."); + } + + @Test + @DisplayName("run() should print error when output directory is a file") + void run_WhenOutputDirIsAFile_ThenPrintError() throws IOException { + // Given + Path filePath = testOutPath.resolve("iamAFile.txt"); + Files.createFile(filePath); + String filePathString = filePath.toFile().getAbsolutePath(); + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", filePathString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: '" + filePathString + "' is not a directory."); + } + + @Test + @DisplayName("run() should print error when input CML file does not exist") + void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { + // Given + String nonExistingFile = "just-a-file-that-does-not-exist.cml"; + String[] args = { "generate", "-i", nonExistingFile, "-g", "plantuml", "-o", testOutDirString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: The file '" + nonExistingFile + "' does not exist."); + } + + @Test + @DisplayName("run() should print error when input file is not a CML file") + void run_WhenInputFileIsNotCML_ThenPrintError() throws IOException { + // Given + Path tempFile = testOutPath.resolve("test.txt"); + Files.createFile(tempFile); + String existingNonCmlFile = tempFile.toFile().getAbsolutePath(); + String[] args = { "generate", "-i", existingNonCmlFile, "-g", "plantuml", "-o", testOutDirString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Please provide a path to a CML (*.cml) file."); + } + + @Test + @DisplayName("run() should generate PlantUML files when plantuml generator is specified") + void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", testOutDirString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, "test_BC_CargoBookingContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_BC_LocationContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_BC_VoyagePlanningContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.puml")).exists(); + } + + @Test + @DisplayName("run() should generate Context Map files when context-map generator is specified") + void run_WhenCalledWithContextMapParam_ThenGenerateContextMapFiles() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "context-map", "-o", + testOutDirString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, "test_ContextMap.gv")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.png")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.svg")).exists(); + } + + @Test + @DisplayName("run() should generate generic output when generic generator and template are specified") + void run_WhenCalledWithGenericParam_ThenGenerateGenericOutput() { + // Given + String outputFileName = "test.md"; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-t", "src/test/resources/test.ftl", "-f", outputFileName }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, outputFileName)).exists(); + } + + @Test + @DisplayName("run() should print error when generic generator is missing the template parameter") + void run_WhenGenericGeneratorMissingTemplate_ThenPrintError() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-f", "test.md" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("The --template (-t) parameter is required for the 'generic' generator."); + } + + @Test + @DisplayName("run() should print error when generic generator is missing the output file parameter") + void run_WhenGenericGeneratorMissingOutputFile_ThenPrintError() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-t", "src/test/resources/test.ftl" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("The --outputFile (-f) parameter is required for the 'generic' generator."); + } + + @Test + @DisplayName("run() should print error and help when required options are missing") + void run_WhenRequiredOptionsMissing_ThenPrintErrorAndHelp() { + // Given + String[] args = { "generate" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Missing required option: '--input='") + .contains("Usage: cm generate [-hV] [-f=] [-g=]"); + } +} diff --git a/src/test/java/org/contextmapper/cli/ValidateCommandTest.java b/src/test/java/org/contextmapper/cli/ValidateCommandTest.java new file mode 100644 index 0000000..7c12edb --- /dev/null +++ b/src/test/java/org/contextmapper/cli/ValidateCommandTest.java @@ -0,0 +1,126 @@ +package org.contextmapper.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import picocli.CommandLine; + +class ValidateCommandTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + private CommandLine cmd; + + @BeforeEach + void setUp() { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + cmd = new CommandLine(new ContextMapperCLI()); + } + + @AfterEach + void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + @Test + @DisplayName("run() should print help when called with -h option") + void run_WhenCalledWithHelp_ThenPrintHelp() { + // Given + String[] args = { "validate", "-h" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm validate [-hV] -i=") + .contains("Validates a CML file."); + } + + @Test + @DisplayName("run() should validate successfully for a valid CML file") + void run_WhenWithValidCMLFile_ThenValidateWithoutErrors() { + // Given + String[] args = { "validate", "-i", "src/test/resources/test.cml" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("The CML file 'src/test/resources/test.cml' has been validated without errors."); + } + + @Test + @DisplayName("run() should print error for an invalid CML file") + void run_WhenWithInvalidCMLFile_ThenPrintError() { + // Given + String[] args = { "validate", "-i", "src/test/resources/test-with-error.cml" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()) + .contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); + } + + @Test + @DisplayName("run() should print error when input file does not exist") + void run_WhenInputFileDoesNotExist_ThenPrintError() { + // Given + String nonExistingFile = "nonexistent.cml"; + String[] args = { "validate", "-i", nonExistingFile }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: The file '" + nonExistingFile + "' does not exist."); + } + + @Test + @DisplayName("run() should print error when input file is not a CML file") + void run_WhenInputFileIsNotCML_ThenPrintError() { + // Given + String notACmlFile = "src/test/resources/test.txt"; + String[] args = { "validate", "-i", notACmlFile }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Please provide a path to a CML (*.cml) file."); + } + + @Test + @DisplayName("run() should print error and help when no input file is provided") + void run_WhenNoInputFileProvided_ThenPrintErrorAndHelp() { + // Given + String[] args = { "validate" }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Missing required option: '--input='") + .contains("Usage: cm validate [-hV] -i="); + } +} diff --git a/src/test/java/org/contextmapper/cli/VersionProviderTest.java b/src/test/java/org/contextmapper/cli/VersionProviderTest.java new file mode 100644 index 0000000..7975b25 --- /dev/null +++ b/src/test/java/org/contextmapper/cli/VersionProviderTest.java @@ -0,0 +1,72 @@ +package org.contextmapper.cli; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class VersionProviderTest { + + static class TestableVersionProvider extends VersionProvider { + private final Package mockPackage; + + TestableVersionProvider(Package mockPackage) { + this.mockPackage = mockPackage; + } + + @Override + protected Package getPackageToInspect() { + return mockPackage; + } + } + + @Test + void getVersion_whenImplVersionIsPresent_returnsVersionString() throws Exception { + // Arrange + String expectedVersion = "1.2.3"; + Package mockPackage = Mockito.mock(Package.class); + Mockito.when(mockPackage.getImplementationVersion()).thenReturn(expectedVersion); + VersionProvider versionProvider = new TestableVersionProvider(mockPackage); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI v" + expectedVersion); + } + + @Test + void getVersion_whenImplVersionIsNull_returnsDevelopmentVersionString() throws Exception { + // Arrange + Package mockPackage = Mockito.mock(Package.class); + Mockito.when(mockPackage.getImplementationVersion()).thenReturn(null); + VersionProvider versionProvider = new TestableVersionProvider(mockPackage); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + } + + @Test + void getVersion_whenPackageIsNull_returnsDevelopmentVersionString() throws Exception { + // Arrange + VersionProvider versionProvider = new TestableVersionProvider(null); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + } +} \ No newline at end of file diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java deleted file mode 100644 index 20149b6..0000000 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.contextmapper.cli.commands; - -import org.eclipse.xtext.generator.IGenerator2; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -class ContextMapperGeneratorTest { - - @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when - final String name = generator.getName(); - - // then - assertThat(name) - .isNotNull() - .isNotEmpty(); - } - - @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void getDescription_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorDescription(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when - final String description = generator.getDescription(); - - // then - assertThat(description) - .isNotNull() - .isNotEmpty(); - } - - @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void toString_WhenUsingGeneratorEnumValue_ThenCanGetStringRepresentation(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when - final String stringRepresentation = generator.toString(); - - // then - assertThat(stringRepresentation) - .isNotNull() - .isNotEmpty(); - } - - @ParameterizedTest - @ValueSource(strings = {"context-map", "plantuml", "generic"}) - void byName_WhenWithValidName_ThenReturnGenerator(final String validGeneratorKey) { - // when - final ContextMapperGenerator generator = ContextMapperGenerator.byName(validGeneratorKey); - - // then - assertThat(generator).isNotNull(); - } - - @Test - void byName_WhenWithoutName_ThenThrowIllegalArgumentException() { - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName(null)); - } - - @Test - void byName_WhenWithEmptyName_ThenThrowIllegalArgumentException() { - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("")); - } - - @Test - void byName_WhenWithInvalidName_ThenThrowIllegalArgumentException() { - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("just a string")); - } - - @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void getGenerator_WhenCalled_ThenReturnGeneratorImplementation(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when - final IGenerator2 generatorImpl = generator.getGenerator(); - - // then - assertThat(generatorImpl).isNotNull(); - } - -} diff --git a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java deleted file mode 100644 index 09a57ff..0000000 --- a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class GenerateCommandTest { - - private static final String TEST_OUT_DIR = "build/test-out"; - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - private final PrintStream originalErr = System.err; - - private File testOutDir; - - @BeforeEach - public void setUpStreams() throws IOException { - testOutDir = new File(TEST_OUT_DIR); - if (testOutDir.exists()) { - Files.walk(testOutDir.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - testOutDir.mkdir(); - - System.setOut(new PrintStream(outContent)); - System.setErr(new PrintStream(errContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - System.setErr(originalErr); - } - - @ParameterizedTest - @ValueSource(strings = {"-h", "--help", "-i some-file.cml -g plantuml -h", "-i some-file.cml -g plantuml --help"}) - void run_WhenCalledWithHelp_ThenPrintHelp(final String params) { - // given - final GenerateCommand command = spy(new GenerateCommand()); - - // when - command.run(params.split(" ")); - - // then - verify(command).printHelp(any()); - } - - @Test - void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "/just-some-dir-that-hopefully-not-exists"}); - - // then - assertThat(outContent.toString()).contains("ERROR: '/just-some-dir-that-hopefully-not-exists' is not a directory."); - } - - @Test - void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - - // when - command.run(new String[]{"-i", "just-a-file.cml", "-g", "plantuml", "-o", "/build"}); - - // then - assertThat(outContent.toString()).contains("ERROR: The file 'just-a-file.cml' does not exist."); - } - - @Test - void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "build/test-out"}); - - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test_BC_CargoBookingContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_BC_LocationContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_BC_VoyagePlanningContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.puml").exists()).isTrue(); - } - - @Test - void run_WhenCalledWithContextMapParam_ThenGenerateContextMapFiles() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "context-map", "-o", "build/test-out"}); - - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test_ContextMap.gv").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.png").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.svg").exists()).isTrue(); - } - - @Test - void run_WhenCalledWithGenericParam_ThenGenerateGenericOutput() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "generic", "-o", "build/test-out", "-t", "src/test/resources/test.ftl", "-f", "test.md"}); - - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test.md").exists()).isTrue(); - } - -} diff --git a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java deleted file mode 100644 index 217099a..0000000 --- a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class ValidateCommandTest { - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - private final PrintStream originalErr = System.err; - - @BeforeEach - public void setUpStreams() { - System.setOut(new PrintStream(outContent)); - System.setErr(new PrintStream(errContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - System.setErr(originalErr); - } - - @ParameterizedTest - @ValueSource(strings = {"-h", "--help", "-i some-file.cml -h", "-i some-file.cml --help"}) - void run_WhenCalledWithHelp_ThenPrintHelp(final String params) { - // given - final ValidateCommand command = spy(new ValidateCommand()); - - // when - command.run(params.split(" ")); - - // then - verify(command).printHelp(any()); - } - - @Test - void run_WhenWithValidCMLFile_ThenValidateWithoutErrors() { - // given - final ValidateCommand command = spy(new ValidateCommand()); - - // when - command.run(new String[]{"-i src/test/resources/test.cml"}); - - // then - verify(command).printValidationMessages(any(), any()); - assertThat(outContent.toString()).contains("The CML file 'src/test/resources/test.cml' has been validated without errors."); - } - - @Test - void run_WhenWithInvalidCMLFile_ThenPrintError() { - // given - final ValidateCommand command = spy(new ValidateCommand()); - - // when - command.run(new String[]{"-i src/test/resources/test-with-error.cml"}); - - // then - verify(command).printValidationMessages(any(), any()); - assertThat(outContent.toString()).contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); - } - -} diff --git a/src/test/resources/test.txt b/src/test/resources/test.txt new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/test/resources/test.txt @@ -0,0 +1 @@ + \ No newline at end of file