2121import java .util .List ;
2222import java .util .Random ;
2323
24+ import okhttp3 .mockwebserver .Dispatcher ;
25+ import okhttp3 .mockwebserver .MockResponse ;
26+ import okhttp3 .mockwebserver .MockWebServer ;
27+ import okhttp3 .mockwebserver .RecordedRequest ;
2428import org .dspace .AbstractIntegrationTestWithDatabase ;
2529import org .dspace .authorize .AuthorizeException ;
2630import org .dspace .builder .ItemBuilder ;
4347/**
4448 * Test for checkhandles curation task.
4549 *
50+ * <p>The handle URLs are served by a local {@link MockWebServer} instead of the live handle resolver
51+ * (http://hdl.handle.net/), which the task contacts over HTTP. Hitting the live resolver made these tests
52+ * flaky: when the network was slow the HEAD request timed out and the task reported {@code 617}
53+ * (SocketTimeoutException) instead of the expected status. The mock dispatcher returns deterministic
54+ * responses keyed by path.</p>
55+ *
4656 * @author mkuchtiak
4757 */
4858public class ItemHandleCheckerIT extends AbstractIntegrationTestWithDatabase {
@@ -55,11 +65,10 @@ public class ItemHandleCheckerIT extends AbstractIntegrationTestWithDatabase {
5565 private static final String HANDLE_ITEM3 = HANDLE_COLLECTION + "-3" ;
5666 private static final String HANDLE_ITEM4 = HANDLE_COLLECTION + "-4" ;
5767 private static final String HANDLE_NON_EXISTING = HANDLE_COLLECTION + "-999" ;
58- private static final String HANDLE_URL_REAL = "http://hdl. handle.net/20.1000/5555" ;
59- private static final String HANDLE_INVALID = HANDLE_URL_REAL + "/..??^^/ " ;
68+ // Path (relative to the mock server) of a handle that the resolver answers with a 302 redirect to a 200 page.
69+ private static final String HANDLE_REDIRECT_PATH = "20.1000/5555 " ;
6070 private static final String HANDLE_IGNORED_1 = "11234/998" ;
6171 private static final String HANDLE_IGNORED_2 = "11234/999" ;
62- private static final String HANDLE_URL_IGNORED = "http://hdl.handle.net/" + HANDLE_IGNORED_2 ;
6372
6473 protected CommunityService communityService = ContentServiceFactory .getInstance ().getCommunityService ();
6574 protected CollectionService collectionService = ContentServiceFactory .getInstance ().getCollectionService ();
@@ -77,15 +86,50 @@ public class ItemHandleCheckerIT extends AbstractIntegrationTestWithDatabase {
7786 private Curator curator ;
7887 private CuratorReportTest .ListReporter reporter ;
7988
89+ // Local stand-in for the handle resolver. Started in setUp(); its base URL becomes handle.canonical.prefix.
90+ private MockWebServer mockHandleServer ;
91+ // Previous handle.canonical.prefix, captured in setUp and restored in destroy so the shared config is not
92+ // left pointing at the now-closed mock server for later tests in the same JVM.
93+ private String originalHandlePrefix ;
94+ // URLs that point at the mock server (computed from its dynamic port in setUp).
95+ private String handleUrlReal ;
96+ private String handleUrlRedirectTarget ;
97+ private String handleInvalid ;
98+ private String handleUrlIgnored ;
99+
80100 @ Before
81101 @ Override
82102 public void setUp () throws Exception {
83103 super .setUp ();
84104 CoreServiceFactory .getInstance ().getPluginService ().clearNamedPluginClasses ();
85105 try {
106+ // Serve handle URLs from a local mock server so the task never contacts the live resolver.
107+ mockHandleServer = new MockWebServer ();
108+ String baseUrl = mockHandleServer .url ("/" ).toString ();
109+ handleUrlReal = baseUrl + HANDLE_REDIRECT_PATH ;
110+ handleUrlRedirectTarget = baseUrl + HANDLE_REDIRECT_PATH + "-target" ;
111+ handleInvalid = handleUrlReal + "/..??^^/" ;
112+ handleUrlIgnored = baseUrl + HANDLE_IGNORED_2 ;
113+ mockHandleServer .setDispatcher (new Dispatcher () {
114+ @ Override
115+ public MockResponse dispatch (RecordedRequest request ) {
116+ String path = request .getPath ();
117+ if (("/" + HANDLE_REDIRECT_PATH ).equals (path )) {
118+ // a "real" handle: 302 redirect; the task follows redirects manually via the Location header
119+ return new MockResponse ().setResponseCode (302 ).setHeader ("Location" , handleUrlRedirectTarget );
120+ }
121+ if (("/" + HANDLE_REDIRECT_PATH + "-target" ).equals (path )) {
122+ return new MockResponse ().setResponseCode (200 );
123+ }
124+ // any other (well-formed, non-ignored) handle URL is treated as "not found"
125+ return new MockResponse ().setResponseCode (404 );
126+ }
127+ });
128+
86129 //we have to create a new community in the database
87130 context .turnOffAuthorisationSystem ();
88- cfg .setProperty ("handle.canonical.prefix" , "http://hdl.handle.net/" );
131+ originalHandlePrefix = cfg .getProperty ("handle.canonical.prefix" );
132+ cfg .setProperty ("handle.canonical.prefix" , baseUrl );
89133 cfg .setProperty ("curate.checklist.ignore" , HANDLE_IGNORED_1 + "," + HANDLE_IGNORED_2 );
90134
91135 this .parentCommunity = communityService .create (null , context );
@@ -131,7 +175,7 @@ public void testItemHandleNotFound() throws IOException {
131175
132176 @ Test
133177 public void testItemHandleRedirected () throws IOException {
134- replaceHandleUrl (item2 , HANDLE_URL_REAL );
178+ replaceHandleUrl (item2 , handleUrlReal );
135179 curator .curate (context , HANDLE_ITEM2 );
136180 assertEquals ("Curation should succeed" , Curator .CURATE_SUCCESS , curator .getStatus (TASK_NAME ));
137181 assertTrue (curator .getResult (TASK_NAME ).contains (redirectedResultForItem (item2 )));
@@ -150,18 +194,18 @@ public void testNonExistingHandle() throws IOException {
150194
151195 @ Test
152196 public void testInvalidHandleUrl () throws IOException {
153- replaceHandleUrl (item3 , HANDLE_INVALID );
197+ replaceHandleUrl (item3 , handleInvalid );
154198 curator .curate (context , HANDLE_ITEM3 );
155199 assertEquals ("Curation should fail" , Curator .CURATE_FAIL , curator .getStatus (TASK_NAME ));
156200 String singleReport = reporter .getReport ().get (0 );
157- assertTrue (singleReport .contains (HANDLE_INVALID + " = 500 - FAILED\n " ));
201+ assertTrue (singleReport .contains (handleInvalid + " = 500 - FAILED\n " ));
158202 assertTrue (singleReport .contains ("Error: java.net.URISyntaxException: Illegal character" ));
159203 reporter .getReport ().clear ();
160204 }
161205
162206 @ Test
163207 public void testHandleUrlIgnored () throws IOException {
164- replaceHandleUrl (item4 , HANDLE_URL_IGNORED );
208+ replaceHandleUrl (item4 , handleUrlIgnored );
165209 curator .curate (context , HANDLE_ITEM4 );
166210 assertEquals ("Curation should skip" , Curator .CURATE_SKIP , curator .getStatus (TASK_NAME ));
167211 assertEquals ("Item: " + HANDLE_ITEM4 + "\n " , reporter .getReport ().get (0 ));
@@ -170,9 +214,9 @@ public void testHandleUrlIgnored() throws IOException {
170214
171215 @ Test
172216 public void testCurateCollection () throws IOException {
173- replaceHandleUrl (item2 , HANDLE_URL_REAL );
174- replaceHandleUrl (item3 , HANDLE_INVALID );
175- replaceHandleUrl (item4 , HANDLE_URL_IGNORED );
217+ replaceHandleUrl (item2 , handleUrlReal );
218+ replaceHandleUrl (item3 , handleInvalid );
219+ replaceHandleUrl (item4 , handleUrlIgnored );
176220 curator .curate (context , HANDLE_COLLECTION );
177221 // the final curator status is derived from the status of the latest checked item
178222 // so the final curator status is unpredictable
@@ -184,14 +228,21 @@ public void testCurateCollection() throws IOException {
184228 both (
185229 containsString ("Item: " + item2 .getHandle ())).and (containsString (" = 200 - OK\n " )
186230 ), // item2
187- containsString (HANDLE_INVALID + " = 500 - FAILED" ), // item 3
231+ containsString (handleInvalid + " = 500 - FAILED" ), // item 3
188232 is ("Item: " + HANDLE_ITEM4 + "\n " ) // item 4 (ignored)
189233 ));
190234 }
191235
192236 @ After
193237 @ Override
194238 public void destroy () throws Exception {
239+ if (mockHandleServer != null ) {
240+ mockHandleServer .close ();
241+ }
242+ // restore the shared config so a later test is not left pointing at the now-closed mock server
243+ if (originalHandlePrefix != null ) {
244+ cfg .setProperty ("handle.canonical.prefix" , originalHandlePrefix );
245+ }
195246 // remove all registered handles properly
196247 identifierService .delete (context , item1 , HANDLE_ITEM1 );
197248 identifierService .delete (context , item2 , HANDLE_ITEM2 );
0 commit comments