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