1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.cli;
7   
8   import static org.assertj.core.api.Assertions.assertThat;
9   import static org.junit.jupiter.api.Assertions.assertAll;
10  import static org.junit.jupiter.api.Assertions.assertEquals;
11  import static org.junit.jupiter.api.Assertions.assertNull;
12  
13  import gov.nist.secauto.metaschema.cli.processor.ExitCode;
14  import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
15  
16  import org.junit.jupiter.api.Test;
17  import org.junit.jupiter.params.ParameterizedTest;
18  import org.junit.jupiter.params.provider.Arguments;
19  import org.junit.jupiter.params.provider.MethodSource;
20  
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.stream.Stream;
24  
25  import edu.umd.cs.findbugs.annotations.NonNull;
26  import nl.altindag.log.LogCaptor;
27  
28  /**
29   * Unit test for simple CLI.
30   */
31  public class CLITest {
32    private static final ExitCode NO_EXCEPTION_CLASS = null;
33  
34    void evaluateResult(@NonNull ExitStatus status, @NonNull ExitCode expectedCode) {
35      status.generateMessage(true);
36      assertAll(() -> assertEquals(expectedCode, status.getExitCode(), "exit code mismatch"),
37          () -> assertNull(status.getThrowable(), "expected null Throwable"));
38    }
39  
40    void evaluateResult(@NonNull ExitStatus status, @NonNull ExitCode expectedCode,
41        @NonNull Class<? extends Throwable> thrownClass) {
42      Throwable thrown = status.getThrowable();
43      assertAll(
44          () -> assertEquals(expectedCode, status.getExitCode(), "exit code mismatch"),
45          () -> assertEquals(thrownClass, thrown == null ? null : thrown.getClass(), "expected Throwable mismatch"));
46    }
47  
48    private static Stream<Arguments> providesValues() {
49      @SuppressWarnings("serial")
50      List<Arguments> values = new LinkedList<>() {
51        {
52          add(Arguments.of(new String[] {}, ExitCode.INVALID_COMMAND,
53              NO_EXCEPTION_CLASS));
54          add(Arguments.of(new String[] { "-h" }, ExitCode.OK, NO_EXCEPTION_CLASS));
55          add(Arguments.of(new String[] { "generate-schema", "--help" }, ExitCode.OK,
56              NO_EXCEPTION_CLASS));
57          add(Arguments.of(new String[] { "generate-diagram", "--help" }, ExitCode.OK,
58              NO_EXCEPTION_CLASS));
59          add(Arguments.of(new String[] { "validate", "--help" }, ExitCode.OK,
60              NO_EXCEPTION_CLASS));
61          add(Arguments.of(new String[] { "validate-content", "--help" }, ExitCode.OK,
62              NO_EXCEPTION_CLASS));
63          add(Arguments.of(new String[] { "convert", "--help" }, ExitCode.OK,
64              NO_EXCEPTION_CLASS));
65          add(Arguments.of(new String[] { "metapath", "list-functions", "--help" }, ExitCode.OK,
66              NO_EXCEPTION_CLASS));
67          add(Arguments.of(new String[] { "metapath", "eval", "--help" }, ExitCode.OK,
68              NO_EXCEPTION_CLASS));
69          add(Arguments.of(
70              new String[] { "validate",
71                  "../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml"
72              },
73              ExitCode.OK, NO_EXCEPTION_CLASS));
74          add(Arguments.of(
75              new String[] { "generate-schema", "--overwrite", "--as",
76                  "JSON",
77                  "../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml",
78                  "target/schema-test.json" },
79              ExitCode.OK, NO_EXCEPTION_CLASS));
80          add(Arguments.of(
81              new String[] { "validate-content", "--as=xml",
82                  "-m=../databind/src/test/resources/metaschema/bad_index-has-key/metaschema.xml",
83                  "../databind/src/test/resources/metaschema/bad_index-has-key/example.xml",
84                  "--show-stack-trace" },
85              ExitCode.FAIL, NO_EXCEPTION_CLASS));
86          add(Arguments.of(
87              new String[] { "validate-content", "--as=json",
88                  "-m=../databind/src/test/resources/metaschema/bad_index-has-key/metaschema.xml",
89                  "../databind/src/test/resources/metaschema/bad_index-has-key/example.json", "--show-stack-trace" },
90              ExitCode.FAIL, NO_EXCEPTION_CLASS));
91          add(Arguments.of(
92              new String[] { "validate",
93                  "../databind/src/test/resources/metaschema/simple/metaschema.xml",
94                  "--show-stack-trace" },
95              ExitCode.OK, NO_EXCEPTION_CLASS));
96          add(Arguments.of(
97              new String[] { "generate-schema",
98                  "../databind/src/test/resources/metaschema/simple/metaschema.xml",
99                  "--as", "xml",
100             },
101             ExitCode.OK, NO_EXCEPTION_CLASS));
102         add(Arguments.of(
103             new String[] { "generate-schema",
104                 "../databind/src/test/resources/metaschema/simple/metaschema.xml",
105                 "--as", "json",
106             },
107             ExitCode.OK, NO_EXCEPTION_CLASS));
108         add(Arguments.of(
109             new String[] { "generate-diagram",
110                 "../databind/src/test/resources/metaschema/simple/metaschema.xml"
111             },
112             ExitCode.OK, NO_EXCEPTION_CLASS));
113         add(Arguments.of(
114             new String[] { "validate-content",
115                 "-m",
116                 "../databind/src/test/resources/metaschema/simple/metaschema.xml",
117                 "../databind/src/test/resources/metaschema/simple/example.json",
118                 "--as=json"
119             },
120             ExitCode.OK, NO_EXCEPTION_CLASS));
121         add(Arguments.of(
122             new String[] { "validate-content",
123                 "-m",
124                 "../databind/src/test/resources/metaschema/simple/metaschema.xml",
125                 "../databind/src/test/resources/metaschema/simple/example.xml",
126                 "--as=xml"
127             },
128             ExitCode.OK, NO_EXCEPTION_CLASS));
129         add(Arguments.of(
130             new String[] { "validate-content",
131                 "-m",
132                 "../databind/src/test/resources/metaschema/simple/metaschema.xml",
133                 "https://bad.domain.example.net/example.xml",
134                 "--as=xml"
135             },
136             ExitCode.IO_ERROR, java.net.UnknownHostException.class));
137         add(Arguments.of(
138             new String[] { "validate-content",
139                 "-m",
140                 "../databind/src/test/resources/metaschema/simple/metaschema.xml",
141                 "https://github.com/no-example.xml",
142                 "--as=xml"
143             },
144             ExitCode.IO_ERROR, java.io.FileNotFoundException.class));
145         add(Arguments.of(
146             new String[] { "validate-content",
147                 "-m",
148                 "src/test/resources/content/schema-validation-module.xml",
149                 "src/test/resources/content/schema-validation-module-missing-required.xml",
150                 "--as=xml"
151             },
152             // fail due to schema validation issue
153             ExitCode.FAIL, NO_EXCEPTION_CLASS));
154         add(Arguments.of(
155             new String[] { "validate-content",
156                 "-m",
157                 "src/test/resources/content/schema-validation-module.xml",
158                 "src/test/resources/content/schema-validation-module-missing-required.xml",
159                 "--as=xml",
160                 "--disable-schema-validation"
161             },
162             // fail due to missing element during parsing
163             ExitCode.FAIL, NO_EXCEPTION_CLASS));
164         add(Arguments.of(
165             new String[] { "validate-content",
166                 "-m",
167                 "src/test/resources/content/schema-validation-module.xml",
168                 "src/test/resources/content/schema-validation-module-missing-required.xml",
169                 "--as=xml",
170                 "--disable-schema-validation",
171                 "--disable-constraint-validation"
172             },
173             ExitCode.OK, NO_EXCEPTION_CLASS));
174         add(Arguments.of(
175             new String[] { "metapath", "list-functions" },
176             ExitCode.OK, NO_EXCEPTION_CLASS));
177         add(Arguments.of(
178             new String[] { "convert",
179                 "-m",
180                 "../core/metaschema/schema/metaschema/metaschema-module-metaschema.xml",
181                 "--to=yaml",
182                 "../core/metaschema/schema/metaschema/metaschema-module-metaschema.xml",
183             },
184             ExitCode.OK, NO_EXCEPTION_CLASS));
185       }
186     };
187     return values.stream();
188   }
189 
190   @ParameterizedTest
191   @MethodSource("providesValues")
192   void testAllCommands(@NonNull String[] args, @NonNull ExitCode expectedExitCode,
193       Class<? extends Throwable> expectedThrownClass) {
194     String[] defaultArgs = { "--show-stack-trace" };
195     String[] fullArgs = Stream.of(args, defaultArgs).flatMap(Stream::of)
196         .toArray(String[]::new);
197     if (expectedThrownClass == null) {
198       evaluateResult(CLI.runCli(fullArgs), expectedExitCode);
199     } else {
200       evaluateResult(CLI.runCli(fullArgs), expectedExitCode, expectedThrownClass);
201     }
202   }
203 
204   @Test
205   void test() {
206     try (LogCaptor captor = LogCaptor.forRoot()) {
207       String[] cliArgs = { "validate-content",
208           "-m",
209           "src/test/resources/content/215-module.xml",
210           "src/test/resources/content/215.xml",
211           "--disable-schema-validation"
212       };
213       CLI.runCli(cliArgs);
214       assertThat(captor.getErrorLogs().toString())
215           .contains("expect-default-non-zero: Expect constraint '. > 0' did not match the data",
216               "expect-custom-non-zero: No default message, custom error message for expect-custom-non-zero constraint.",
217               "matches-default-regex-letters-only: Value '1' did not match the pattern",
218               "matches-custom-regex-letters-only: No default message, custom error message for matches-custom-regex-letters-only constraint.",
219               "cardinality-default-two-minimum: The cardinality '1' is below the required minimum '2' for items matching",
220               "index-items-default: Index 'index-items-default' has duplicate key for items",
221               "index-items-custom: No default message, custom error message for index-item-custom.",
222               "is-unique-default: Unique constraint violation at paths",
223               "is-unique-custom: No default message, custom error message for is-unique-custom.",
224               "index-has-key-default: Key reference [2] not found in index 'index-items-default' for item",
225               "index-has-key-custom: No default message, custom error message for index-has-key-custom.");
226     }
227   }
228 }