1 package net.avcompris.commons3.api.tests;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.base.Preconditions.checkState;
5 import static com.google.common.collect.Maps.newHashMap;
6 import static java.util.Locale.ENGLISH;
7 import static net.avcompris.commons3.api.tests.DataUtils.getPropertyAsString;
8 import static net.avcompris.commons3.api.tests.ExpressionUtils.compute;
9 import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
10 import static org.apache.commons.lang3.StringUtils.capitalize;
11 import static org.apache.commons.lang3.StringUtils.substringAfter;
12 import static org.apache.commons.lang3.StringUtils.substringBetween;
13 import static org.junit.jupiter.api.Assertions.assertEquals;
14 import static org.junit.jupiter.api.Assertions.assertNotNull;
15
16 import java.lang.reflect.InvocationTargetException;
17 import java.lang.reflect.Method;
18 import java.util.Arrays;
19 import java.util.Map;
20
21 import javax.annotation.Nullable;
22
23 import org.apache.commons.lang3.NotImplementedException;
24 import org.junit.jupiter.params.ParameterizedTest;
25 import org.junit.jupiter.params.provider.MethodSource;
26
27 import com.google.common.collect.ImmutableMap;
28
29 import net.avcompris.commons3.api.tests.TestsSpec.Assertion;
30 import net.avcompris.commons3.api.tests.TestsSpec.AssertionType;
31 import net.avcompris.commons3.api.tests.TestsSpec.AuthenticationSpec;
32 import net.avcompris.commons3.api.tests.TestsSpec.Data;
33 import net.avcompris.commons3.api.tests.TestsSpec.HttpMethod;
34 import net.avcompris.commons3.api.tests.TestsSpec.Let;
35 import net.avcompris.commons3.api.tests.TestsSpec.Step;
36 import net.avcompris.commons3.api.tests.TestsSpec.TestSpec;
37
38 public abstract class AbstractApiTest {
39
40 protected final TestSpec spec;
41
42 @Nullable
43 protected final String superadminAuthorization;
44
45 protected AbstractApiTest(final TestSpec spec,
46 @Nullable final String superadminAuthorization) {
47
48 this.spec = checkNotNull(spec, "spec");
49
50 this.superadminAuthorization = superadminAuthorization;
51 }
52
53 protected interface StepExecution {
54
55 @Nullable
56 AuthenticationSpec getAuthentication();
57
58 HttpMethod getHttpMethod();
59
60 String getPath();
61
62 Data[] getData();
63
64 @Nullable
65 Class<?> getReturnType();
66 }
67
68 protected static final class StepExecutionResult {
69
70 public final int statusCode;
71
72 @Nullable
73 public final Object result;
74
75 public StepExecutionResult(final int statusCode, @Nullable final Object result) {
76
77 this.statusCode = statusCode;
78 this.result = result;
79 }
80 }
81
82 protected abstract StepExecutionResult execute(int stepIndex, StepExecution stepExecution) throws Exception;
83
84 private static final class StepExecutionImpl implements StepExecution {
85
86 private final Step step;
87 private final String path;
88 private final Map<String, String> variables;
89
90 public StepExecutionImpl(final Step step, final Map<String, String> variables) {
91
92 this(step, step.getPath(), variables);
93 }
94
95 public StepExecutionImpl(final Step step, final String path, final Map<String, String> variables) {
96
97 this.step = checkNotNull(step, "step");
98 this.path = checkNotNull(path, "path");
99 this.variables = checkNotNull(variables, "variables");
100 }
101
102 @Override
103 public AuthenticationSpec getAuthentication() {
104 return step.getAuthentication();
105 }
106
107 @Override
108 public HttpMethod getHttpMethod() {
109 return step.getHttpMethod();
110 }
111
112 @Override
113 public String getPath() {
114 return path;
115 }
116
117 @Override
118 public Data[] getData() {
119
120 final Data[] data = Arrays.copyOf(step.getData(), step.getData().length);
121
122 for (int i = 0; i < data.length; ++i) {
123
124 final Data dataItem = data[i];
125
126 final String dataValue = dataItem.getValue().toString();
127
128 if (dataValue.startsWith("$")) {
129
130 final String variableName = substringAfter(dataValue, "$");
131
132 final String variableValue = variables.get(variableName);
133
134 checkState(variableValue != null, "variable should not be null: %s", dataValue);
135
136 data[i] = new Data() {
137
138 @Override
139 public String getName() {
140 return dataItem.getName();
141 }
142
143 @Override
144 public Object getValue() {
145 return variableValue;
146 }
147 };
148 }
149 }
150
151 return data;
152 }
153
154 @Override
155 @Nullable
156 public Class<?> getReturnType() {
157 return step.getReturnType();
158 }
159 }
160
161 @ParameterizedTest(name = "{0}")
162 @MethodSource("testSpecs")
163 public final void test(final TestSpec testSpec) throws Exception {
164
165 final Map<String, String> variables = newHashMap();
166
167 int stepIndex = 0;
168
169 for (final Step step : spec.getSteps()) {
170
171 final String path = step.getPath();
172
173 final StepExecution stepExecution;
174
175 if (path.contains("$")) {
176
177 String newPath = path;
178
179 for (final Let let : step.getLets()) {
180
181 final String variableName = let.getVariableName();
182
183 final String expression = let.getExpression();
184
185 final String processedValue;
186
187 if (expression.startsWith("$random_alpha(") && expression.endsWith(")")) {
188
189 processedValue = randomAlphabetic(Integer.parseInt(substringBetween(expression, "(", ")")))
190 .toLowerCase(ENGLISH);
191
192 } else if (expression.startsWith("$random_email(") && expression.endsWith(")")) {
193
194 processedValue = (randomAlphabetic(8) + "@" + randomAlphabetic(8) + ".com")
195 .toLowerCase(ENGLISH);
196
197 } else if (expression.startsWith("$")) {
198
199 throw new NotImplementedException(
200 "expression: " + expression + ", for variable: " + variableName);
201
202 } else {
203
204 continue;
205 }
206
207 System.out.println("let " + variableName + " = " + processedValue);
208
209 variables.put(variableName, processedValue);
210 }
211
212 for (final String variableName : variables.keySet()) {
213
214 while (newPath.contains("$" + variableName)) {
215
216 newPath = newPath.replace("$" + variableName, variables.get(variableName));
217 }
218 }
219
220 stepExecution = new StepExecutionImpl(step, newPath, variables);
221
222 } else {
223
224 stepExecution = new StepExecutionImpl(step, variables);
225 }
226
227 final StepExecutionResult stepExecutionResult = execute(stepIndex, stepExecution);
228
229 final int statusCode = stepExecutionResult.statusCode;
230
231 @Nullable
232 final Object result = stepExecutionResult.result;
233
234 for (final Let let : step.getLets()) {
235
236 final String variableName = let.getVariableName();
237
238 final String expression = let.getExpression();
239
240 if (expression.startsWith("response.data.")) {
241
242 assertNotNull(result, "Step #" + stepIndex + ": result should not be null");
243
244 final String propertyName = substringAfter(expression, "response.data.");
245
246 final String propertyValue = getPropertyAsString(result, propertyName);
247
248 System.out.println("let " + variableName + " = " + propertyValue);
249
250 variables.put(variableName, propertyValue);
251 }
252 }
253
254 for (final Assertion assertion : step.getAssertions()) {
255
256 final AssertionType assertionType = assertion.getType();
257
258 if (assertionType != AssertionType.EQUALS) {
259 throw new NotImplementedException("assertion.type: " + assertionType);
260 }
261
262 final String left = assertion.getLeft();
263 final String right = assertion.getRight();
264
265 if ("response.status".contentEquals(left)) {
266
267 assertEquals(Integer.parseInt(right), statusCode, "Step #" + stepIndex + ": " + left);
268
269 } else if (left.startsWith("response.data.")) {
270
271 assertNotNull(result, "Step #" + stepIndex + ": result should not be null");
272
273 final String propertyName = substringAfter(left, "response.data.");
274
275 final String propertyValue = getPropertyAsString(result, propertyName);
276
277 assertEquals(
278 compute(right, ImmutableMap.copyOf(variables)),
279 propertyValue,
280 "Step #" + stepIndex + ": " + left);
281
282 } else {
283
284 final String leftValue = compute(left, ImmutableMap.copyOf(variables));
285 final String rightValue = compute(right, ImmutableMap.copyOf(variables));
286
287 assertEquals(
288 rightValue,
289 leftValue,
290 "Step #" + stepIndex + ": " + left + " should be equal to: " + right);
291 }
292 }
293
294 ++stepIndex;
295 }
296 }
297
298 @Nullable
299 private static Method extractGetterOrNull(final Class<?> clazz, final String propertyName) {
300
301 for (final Class<?> i : clazz.getInterfaces()) {
302
303 final Method method = extractGetterOrNull(i, propertyName);
304
305 if (method != null) {
306
307 return method;
308 }
309 }
310
311 final String is = "is" + capitalize(propertyName);
312
313 final String get = "get" + capitalize(propertyName);
314
315 for (final Method method : clazz.getMethods()) {
316
317 if (method.getParameterCount() != 0) {
318 continue;
319 }
320
321 final String methodName = method.getName();
322
323 if (is.contentEquals(methodName) || get.contentEquals(methodName)) {
324 return method;
325 }
326 }
327
328 return null;
329 }
330
331 protected static Method extractGetter(final Class<?> clazz, final String propertyName) {
332
333 final Method method = extractGetterOrNull(clazz, propertyName);
334
335 if (method != null) {
336
337 return method;
338 }
339
340 throw new IllegalArgumentException("clazz: " + clazz.getName() + ", propertyName: " + propertyName);
341 }
342
343 protected static Object invoke(final Method method, final Object dataBean) {
344
345 try {
346
347 return method.invoke(dataBean);
348
349 } catch (final IllegalAccessException e) {
350
351 throw new RuntimeException(e);
352
353 } catch (final InvocationTargetException e) {
354
355 throw new RuntimeException(e.getTargetException());
356 }
357 }
358 }