source: josm/trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java@ 19056

Last change on this file since 19056 was 19056, checked in by taylor.smock, 4 weeks ago

Fix last failing test in Java 21

  • Generate Java 21 image files (probably from r19043: Drop COMPAT locale provider).
  • Replace most deprecated function calls and classes in tests with non-deprecated functions.
File size: 10.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.testutils.annotations;
3
4import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
5import static org.junit.jupiter.api.Assertions.assertTrue;
6import static org.junit.jupiter.api.Assertions.fail;
7
8import java.lang.annotation.Documented;
9import java.lang.annotation.ElementType;
10import java.lang.annotation.Inherited;
11import java.lang.annotation.Retention;
12import java.lang.annotation.RetentionPolicy;
13import java.lang.annotation.Target;
14import java.lang.reflect.Constructor;
15import java.lang.reflect.Field;
16import java.net.MalformedURLException;
17import java.net.URL;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.List;
21import java.util.stream.Collectors;
22
23import org.junit.jupiter.api.extension.AfterAllCallback;
24import org.junit.jupiter.api.extension.AfterEachCallback;
25import org.junit.jupiter.api.extension.BeforeAllCallback;
26import org.junit.jupiter.api.extension.BeforeEachCallback;
27import org.junit.jupiter.api.extension.ExtendWith;
28import org.junit.jupiter.api.extension.ExtensionContext;
29import org.junit.jupiter.api.extension.ParameterContext;
30import org.junit.jupiter.api.extension.ParameterResolutionException;
31import org.junit.jupiter.api.extension.ParameterResolver;
32import org.junit.platform.commons.support.AnnotationSupport;
33import org.openstreetmap.josm.TestUtils;
34import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
35import org.openstreetmap.josm.gui.util.GuiHelper;
36import org.openstreetmap.josm.io.OsmApi;
37import org.openstreetmap.josm.spi.preferences.Config;
38import org.openstreetmap.josm.tools.Logging;
39import org.openstreetmap.josm.tools.Pair;
40import org.openstreetmap.josm.tools.Utils;
41
42import com.github.tomakehurst.wiremock.WireMockServer;
43import com.github.tomakehurst.wiremock.client.WireMock;
44import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2;
45import com.github.tomakehurst.wiremock.verification.LoggedRequest;
46
47/**
48 * Create a basic wiremock environment. If you need the actual WireMockServer, annotate a field or parameter
49 * with {@code @BasicWiremock}.
50 *
51 * @author Taylor Smock
52 * @see OsmApiExtension (this sets the Osm Api to the wiremock URL)
53 * @since 18106
54 */
55@Inherited
56@Documented
57@Retention(RetentionPolicy.RUNTIME)
58@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
59@ExtendWith(BasicWiremock.WireMockExtension.class)
60public @interface BasicWiremock {
61 /**
62 * Set the path for the data. Default is {@link TestUtils#getTestDataRoot()}.
63 * @return The path ({@code ""} for the default)
64 */
65 String value() default "";
66
67 /**
68 * {@link ResponseTransformerV2} for use with the WireMock server.
69 * Current constructors supported:
70 * <ul>
71 * <li>{@code new ResponseTransformer()}</li>
72 * <li>{@code new ResponseTransformer(ExtensionContext context)}</li>
73 * </ul>
74 * @return The transformers to instantiate
75 */
76 Class<? extends ResponseTransformerV2>[] responseTransformers() default {};
77
78 /**
79 * Start/stop WireMock automatically, and check for missed calls.
80 * @author Taylor Smock
81 *
82 */
83 class WireMockExtension
84 implements AfterAllCallback, AfterEachCallback, BeforeAllCallback, BeforeEachCallback, ParameterResolver {
85 /**
86 * Get the default wiremock server
87 * @param context The context to search
88 * @return The wiremock server
89 */
90 static WireMockServer getWiremock(ExtensionContext context) {
91 ExtensionContext.Namespace namespace = ExtensionContext.Namespace.create(BasicWiremock.class);
92 BasicWiremock annotation = AnnotationUtils.findFirstParentAnnotation(context, BasicWiremock.class)
93 .orElseThrow(() -> new IllegalArgumentException("There must be a @BasicWiremock annotation"));
94 return context.getStore(namespace).getOrComputeIfAbsent(WireMockServer.class, clazz -> {
95 final List<ResponseTransformerV2> transformers = new ArrayList<>(annotation.responseTransformers().length);
96 for (Class<? extends ResponseTransformerV2> responseTransformer : annotation.responseTransformers()) {
97 for (Pair<Class<?>[], Object[]> parameterMapping : Arrays.asList(
98 new Pair<>(new Class<?>[] {ExtensionContext.class }, new Object[] {context }),
99 new Pair<>(new Class<?>[0], new Object[0]))) {
100 try {
101 Constructor<? extends ResponseTransformerV2> constructor = responseTransformer
102 .getConstructor(parameterMapping.a);
103 transformers.add(constructor.newInstance(parameterMapping.b));
104 break;
105 } catch (ReflectiveOperationException e) {
106 fail(e);
107 }
108 }
109 }
110 return new WireMockServer(
111 options().usingFilesUnderDirectory(Utils.isStripEmpty(annotation.value()) ? TestUtils.getTestDataRoot() :
112 annotation.value()).extensions(transformers.toArray(new ResponseTransformerV2[0])).dynamicPort());
113 }, WireMockServer.class);
114 }
115
116 /**
117 * Replace URL servers with wiremock
118 *
119 * @param wireMockServer The wiremock to point to
120 * @param url The URL to fix
121 * @return A url that points at the wiremock server
122 */
123 public static String replaceUrl(WireMockServer wireMockServer, String url) {
124 try {
125 URL temp = new URL(url);
126 return wireMockServer.baseUrl() + temp.getFile();
127 } catch (MalformedURLException error) {
128 Logging.error(error);
129 }
130 return null;
131 }
132
133 @Override
134 public void afterAll(ExtensionContext context) throws Exception {
135 // Run in EDT to avoid stopping wiremock server before wiremock requests finish.
136 GuiHelper.runInEDTAndWait(getWiremock(context)::stop);
137 }
138
139 @Override
140 public void afterEach(ExtensionContext context) throws Exception {
141 List<LoggedRequest> missed = getWiremock(context).findUnmatchedRequests().getRequests();
142 missed.forEach(r -> Logging.error(r.getAbsoluteUrl()));
143 try {
144 assertTrue(missed.isEmpty(), missed.stream().map(LoggedRequest::getUrl).collect(Collectors.joining("\n\n")));
145 } finally {
146 getWiremock(context).resetRequests();
147 getWiremock(context).resetToDefaultMappings();
148 getWiremock(context).resetScenarios();
149 if (AnnotationUtils.elementIsAnnotated(context.getElement(), BasicWiremock.class)
150 || getWiremock(context) == null) {
151 this.afterAll(context);
152 }
153 }
154 }
155
156 @Override
157 public void beforeAll(ExtensionContext context) throws Exception {
158 getWiremock(context).start();
159 }
160
161 @Override
162 public void beforeEach(ExtensionContext context) throws Exception {
163 if (AnnotationUtils.elementIsAnnotated(context.getElement(), BasicWiremock.class) || getWiremock(context) == null) {
164 this.beforeAll(context);
165 }
166 if (context.getTestClass().isPresent()) {
167 List<Field> wireMockFields = AnnotationSupport.findAnnotatedFields(context.getRequiredTestClass(), BasicWiremock.class);
168 for (Field field : wireMockFields) {
169 if (WireMockServer.class.isAssignableFrom(field.getType())) {
170 final boolean isAccessible = field.canAccess(context.getRequiredTestInstance());
171 field.setAccessible(true);
172 try {
173 field.set(context.getTestInstance().orElse(null), getWiremock(context));
174 } finally {
175 field.setAccessible(isAccessible);
176 }
177 } else {
178 throw new IllegalArgumentException("@BasicWiremock: cannot set field of type " + field.getType().getName());
179 }
180 }
181 }
182 }
183
184 @Override
185 public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
186 throws ParameterResolutionException {
187 return parameterContext.getParameter().getAnnotation(BasicWiremock.class) != null
188 && parameterContext.getParameter().getType() == WireMockServer.class;
189 }
190
191 @Override
192 public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
193 throws ParameterResolutionException {
194 return getWiremock(extensionContext);
195 }
196 }
197
198 /**
199 * A class specifically to mock OSM API calls
200 */
201 class OsmApiExtension extends WireMockExtension {
202 @Override
203 public void afterAll(ExtensionContext context) throws Exception {
204 try {
205 super.afterAll(context);
206 } finally {
207 Config.getPref().put("osm-server.url", "https://invalid.url");
208 }
209 }
210
211 @Override
212 public void beforeAll(ExtensionContext context) throws Exception {
213 if (!AnnotationSupport.isAnnotated(context.getElement(), BasicPreferences.class)) {
214 fail("OsmApiExtension requires @BasicPreferences");
215 }
216 super.beforeAll(context);
217 Config.getPref().put("osm-server.url", getWiremock(context).baseUrl() + "/api");
218 getWiremock(context).stubFor(WireMock.get("/api/0.6/capabilities")
219 .willReturn(WireMock.aResponse().withBodyFile("api/0.6/capabilities")));
220 getWiremock(context).stubFor(WireMock.get("/api/capabilities")
221 .willReturn(WireMock.aResponse().withBodyFile("api/capabilities")));
222 OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE);
223 }
224 }
225}
Note: See TracBrowser for help on using the repository browser.