View Javadoc
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; // do nothing
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 }