1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.test.web;
18
19 import static java.lang.String.format;
20 import static org.bremersee.test.web.RestApiAssertionType.ANNOTATION_MUST_NOT_BE_NULL;
21 import static org.bremersee.test.web.RestApiAssertionType.CLASS_MUST_BE_INTERFACE;
22 import static org.bremersee.test.web.RestApiAssertionType.METHOD_MUST_NOT_BE_NULL;
23 import static org.bremersee.test.web.RestApiAssertionType.SAME_ANNOTATION_ATTRIBUTES_SIZE;
24 import static org.bremersee.test.web.RestApiAssertionType.SAME_ANNOTATION_ATTRIBUTE_VALUE;
25 import static org.bremersee.test.web.RestApiAssertionType.SAME_ANNOTATION_SIZE;
26 import static org.bremersee.test.web.RestApiAssertionType.SAME_METHOD_SIZE;
27 import static org.bremersee.test.web.RestApiTesterExclusion.isExcluded;
28 import static org.bremersee.test.web.RestApiTesterPath.PathType.ANNOTATION;
29 import static org.bremersee.test.web.RestApiTesterPath.PathType.ATTRIBUTE;
30 import static org.bremersee.test.web.RestApiTesterPath.PathType.CLASS;
31 import static org.bremersee.test.web.RestApiTesterPath.PathType.METHOD;
32 import static org.bremersee.test.web.RestApiTesterPath.PathType.METHOD_PARAMETER;
33 import static org.bremersee.test.web.RestApiTesterPath.pathBuilder;
34 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
35 import static org.junit.jupiter.api.Assertions.assertEquals;
36 import static org.junit.jupiter.api.Assertions.assertNotNull;
37 import static org.junit.jupiter.api.Assertions.assertNull;
38 import static org.junit.jupiter.api.Assertions.assertTrue;
39
40 import java.lang.annotation.Annotation;
41 import java.lang.reflect.AnnotatedElement;
42 import java.lang.reflect.Method;
43 import java.lang.reflect.Parameter;
44 import java.util.Arrays;
45 import java.util.Map;
46 import java.util.stream.Collectors;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.springframework.core.annotation.AnnotationUtils;
50 import org.springframework.util.ReflectionUtils;
51 import org.springframework.util.StringUtils;
52
53
54
55
56
57
58 public class RestApiTester {
59
60 private static final Logger log = LoggerFactory.getLogger(RestApiTester.class);
61
62 private RestApiTester() {
63 }
64
65
66
67
68
69
70
71
72 public static void assertSameApi(
73 final Class<?> expected,
74 final Class<?> actual,
75 final RestApiTesterExclusion... exclusions) {
76
77 log.info("Assert same api: expected = {}, actual = {}", name(expected),
78 name(actual));
79 assertNotNull(expected, "Expected api class must not be null.");
80 assertNotNull(actual, "Actual api class must not be null.");
81
82 RestApiTesterPath path = pathBuilder().add(CLASS, expected.getSimpleName()).build();
83 if (!isExcluded(path, CLASS_MUST_BE_INTERFACE, exclusions)) {
84 log.info("Assert given class is an interface:"
85 + "\n - path = {}"
86 + "\n - type = {}",
87 path, CLASS_MUST_BE_INTERFACE);
88 assertTrue(expected.isInterface(), "Expected api class must be an interface.");
89 }
90
91 path = pathBuilder().add(CLASS, actual.getSimpleName()).build();
92 if (!isExcluded(path, CLASS_MUST_BE_INTERFACE, exclusions)) {
93 log.info("Assert given class is an interface:"
94 + "\n - path = {}"
95 + "\n - type = {}",
96 path, CLASS_MUST_BE_INTERFACE);
97 assertTrue(actual.isInterface(), "Actual api class must be an interface.");
98 }
99
100 assertSameClassAnnotations(expected, actual, exclusions);
101 assertSameMethodAnnotations(expected, actual, exclusions);
102 }
103
104 private static void assertSameClassAnnotations(
105 final Class<?> expected,
106 final Class<?> actual,
107 final RestApiTesterExclusion... exclusions) {
108
109 final Annotation[] expectedAnnotations = expected.getAnnotations();
110 final Annotation[] actualAnnotations = actual.getAnnotations();
111 assertSameAnnotations(
112 pathBuilder().add(CLASS, actual.getSimpleName()).build(),
113 expectedAnnotations,
114 actualAnnotations,
115 actual,
116 exclusions);
117 }
118
119 private static void assertSameMethodAnnotations(
120 final Class<?> expected,
121 final Class<?> actual,
122 final RestApiTesterExclusion... exclusions) {
123
124 final RestApiTesterPath classPath = pathBuilder().add(CLASS, actual.getSimpleName()).build();
125 final Method[] expectedMethods = ReflectionUtils.getDeclaredMethods(expected);
126 final Method[] actualMethods = ReflectionUtils.getDeclaredMethods(actual);
127
128 if (!isExcluded(classPath, SAME_METHOD_SIZE, exclusions)) {
129 log.info("Assert same method size:"
130 + "\n - path = {}"
131 + "\n - type = {}",
132 classPath, SAME_METHOD_SIZE);
133 assertEquals(
134 expectedMethods.length,
135 actualMethods.length,
136 format("Methods must have the same size on %s and %s.",
137 expected.getSimpleName(), actual.getSimpleName()));
138 }
139
140 for (final Method expectedMethod : expectedMethods) {
141 final RestApiTesterPath methodPath = classPath.toPathBuilder()
142 .add(METHOD, expectedMethod.getName())
143 .build();
144 final Method actualMethod = ReflectionUtils.findMethod(
145 actual, expectedMethod.getName(), expectedMethod.getParameterTypes());
146 if (!isExcluded(methodPath, METHOD_MUST_NOT_BE_NULL, exclusions)) {
147 log.info("Assert method exists:"
148 + "\n - path = {}"
149 + "\n - type = {}",
150 methodPath, METHOD_MUST_NOT_BE_NULL);
151 assertNotNull(
152 actualMethod,
153 format("Method %s (%s) is missing on %s", expectedMethod.getName(),
154 parameters(expectedMethod.getParameterTypes()), name(actual)));
155 } else if (actualMethod == null) {
156 continue;
157 }
158
159 final Annotation[] expectedAnnotations = expectedMethod.getAnnotations();
160 final Annotation[] actualAnnotations = actualMethod.getAnnotations();
161 assertSameAnnotations(
162 methodPath,
163 expectedAnnotations,
164 actualAnnotations,
165 actualMethod,
166 exclusions);
167
168 if (expectedMethod.getParameterCount() > 0) {
169 final Parameter[] expectedParameters = expectedMethod.getParameters();
170 final Parameter[] actualParameters = actualMethod.getParameters();
171 for (int i = 0; i < expectedParameters.length; i++) {
172 final Parameter expectedParameter = expectedParameters[i];
173 final Parameter actualParameter = actualParameters[i];
174 final RestApiTesterPath methodParameterPath = methodPath.toPathBuilder()
175 .add(METHOD_PARAMETER, String.valueOf(i))
176 .build();
177 assertSameAnnotations(
178 methodParameterPath,
179 expectedParameter.getAnnotations(),
180 actualParameter.getAnnotations(),
181 actualParameter,
182 exclusions);
183 }
184 }
185 }
186 }
187
188 private static void assertSameAnnotations(
189 final RestApiTesterPath path,
190 final Annotation[] expectedAnnotations,
191 final Annotation[] actualAnnotations,
192 final AnnotatedElement actualAnnotatedElement,
193 final RestApiTesterExclusion... exclusions) {
194
195 if (!isExcluded(path, SAME_ANNOTATION_SIZE, exclusions)) {
196 log.info("Assert same annotation size:"
197 + "\n - path = {}"
198 + "\n - type = {}",
199 path, SAME_ANNOTATION_SIZE);
200 assertEquals(
201 expectedAnnotations.length,
202 actualAnnotations.length,
203 format("Annotations must have the same size on %s.", path));
204 }
205 for (final Annotation expectedAnnotation : expectedAnnotations) {
206 final Annotation actualAnnotation = AnnotationUtils.getAnnotation(
207 actualAnnotatedElement, expectedAnnotation.annotationType());
208 assertSameAnnotation(
209 path,
210 expectedAnnotation,
211 actualAnnotation,
212 exclusions);
213 }
214 }
215
216 private static void assertSameAnnotation(
217 final RestApiTesterPath path,
218 final Annotation expected,
219 final Annotation actual,
220 final RestApiTesterExclusion... exclusions) {
221
222 final RestApiTesterPath annotationPath = path.toPathBuilder()
223 .add(ANNOTATION, expected.annotationType().getSimpleName())
224 .build();
225 if (!isExcluded(annotationPath, ANNOTATION_MUST_NOT_BE_NULL, exclusions)) {
226 log.info("Assert annotation is not null:"
227 + "\n - path = {}"
228 + "\n - type = {}",
229 annotationPath, ANNOTATION_MUST_NOT_BE_NULL);
230 assertNotNull(
231 actual,
232 format("Annotation %s is missing on %s.", name(expected), path));
233 }
234 if (actual == null) {
235 return;
236 }
237 final Map<String, Object> expectedAttributes = AnnotationUtils.getAnnotationAttributes(
238 expected, true, false);
239 final Map<String, Object> actualAttributes = AnnotationUtils.getAnnotationAttributes(
240 actual, true, false);
241 assertSameAttributes(
242 annotationPath,
243 expectedAttributes,
244 actualAttributes,
245 exclusions);
246 }
247
248 private static void assertSameAttributes(
249 final RestApiTesterPath annotationPath,
250 final Map<String, Object> expected,
251 final Map<String, Object> actual,
252 final RestApiTesterExclusion... exclusions) {
253
254 if (!isExcluded(annotationPath, SAME_ANNOTATION_ATTRIBUTES_SIZE, exclusions)) {
255 log.info("Assert same annotation attributes size:"
256 + "\n - path = {}"
257 + "\n - type = {}",
258 annotationPath, SAME_ANNOTATION_ATTRIBUTES_SIZE);
259 assertEquals(
260 expected.size(),
261 actual.size(),
262 format("Attributes of annotation (%s) must have the same size.", annotationPath));
263 }
264 for (final Map.Entry<String, Object> expectedAttribute : expected.entrySet()) {
265 final Object expectedAttributeValue = expectedAttribute.getValue();
266 final Object actualAttributeValue = actual.get(expectedAttribute.getKey());
267 assertSameAttributeValues(
268 annotationPath.toPathBuilder().add(ATTRIBUTE, expectedAttribute.getKey()).build(),
269 expectedAttributeValue,
270 actualAttributeValue,
271 exclusions);
272 }
273 }
274
275 private static void assertSameAttributeValues(
276 final RestApiTesterPath attributePath,
277 final Object expected,
278 final Object actual,
279 final RestApiTesterExclusion... exclusions) {
280
281 if (!isExcluded(attributePath, SAME_ANNOTATION_ATTRIBUTE_VALUE, exclusions)) {
282 log.info("Assert same attribute values:"
283 + "\n - path = {}"
284 + "\n - expected = {}"
285 + "\n - actual = {}"
286 + "\n - type = {}",
287 attributePath, expected, actual, SAME_ANNOTATION_ATTRIBUTE_VALUE);
288
289 if (expected == null) {
290 assertNull(
291 actual,
292 format("Attribute (%s) must be null.", attributePath));
293 } else if (expected instanceof Annotation[]) {
294 assertTrue(actual instanceof Annotation[]);
295 final Annotation[] expectedAnnotations = (Annotation[]) expected;
296 final Annotation[] actualAnnotations = (Annotation[]) actual;
297 assertEquals(expectedAnnotations.length, actualAnnotations.length);
298 for (int i = 0; i < actualAnnotations.length; i++) {
299 final Annotation expectedAnnotation = expectedAnnotations[i];
300 final Annotation actualAnnotation = actualAnnotations[i];
301 assertSameAnnotation(
302 attributePath,
303 expectedAnnotation,
304 actualAnnotation,
305 exclusions);
306 }
307 } else if (expected instanceof Annotation) {
308 final Annotation expectedAnnotation = (Annotation) expected;
309 final Annotation actualAnnotation = (Annotation) actual;
310 assertSameAnnotation(
311 attributePath,
312 expectedAnnotation,
313 actualAnnotation,
314 exclusions);
315 } else {
316 if (expected.getClass().isArray()) {
317 final Object[] expectedArray = (Object[]) expected;
318 final Object[] actualArray = (Object[]) actual;
319 assertArrayEquals(expectedArray, actualArray);
320 } else {
321 assertEquals(
322 expected, actual);
323 }
324 }
325 }
326 }
327
328 private static String name(final Annotation annotation) {
329 return annotation != null ? name(annotation.annotationType()) : "null";
330 }
331
332 private static String name(final Class<?> clazz) {
333 return clazz != null ? clazz.getName() : "null";
334 }
335
336 private static String parameters(final Class<?>[] parameters) {
337 if (parameters == null || parameters.length == 0) {
338 return "";
339 }
340 return StringUtils.collectionToCommaDelimitedString(Arrays.stream(parameters)
341 .map(RestApiTester::name).collect(Collectors.toList()));
342 }
343
344 }