Skip to content

Commit fe8f51b

Browse files
committed
TIKA-4809: pin the reviewed behavior contracts with tests
1 parent 92d2d12 commit fe8f51b

8 files changed

Lines changed: 259 additions & 0 deletions

File tree

tika-core/src/test/java/org/apache/tika/sax/BasicContentHandlerFactoryTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static java.nio.charset.StandardCharsets.UTF_8;
2020
import static org.junit.jupiter.api.Assertions.assertEquals;
2121
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

2425
import java.io.ByteArrayOutputStream;
@@ -54,6 +55,38 @@ public static void assertNotContains(String needle, String haystack) {
5455
assertFalse(haystack.contains(needle), needle + " found in:\n" + haystack);
5556
}
5657

58+
@Test
59+
public void testParseHandlerTypeNullYieldsDefault() {
60+
assertEquals(BasicContentHandlerFactory.HANDLER_TYPE.XML,
61+
BasicContentHandlerFactory.parseHandlerType(null,
62+
BasicContentHandlerFactory.HANDLER_TYPE.XML));
63+
}
64+
65+
@Test
66+
public void testParseHandlerTypeAliases() {
67+
for (String name : new String[]{"text", "txt", "TEXT"}) {
68+
assertEquals(BasicContentHandlerFactory.HANDLER_TYPE.TEXT,
69+
BasicContentHandlerFactory.parseHandlerType(name, null), name);
70+
}
71+
for (String name : new String[]{"markdown", "md", "MD"}) {
72+
assertEquals(BasicContentHandlerFactory.HANDLER_TYPE.MARKDOWN,
73+
BasicContentHandlerFactory.parseHandlerType(name, null), name);
74+
}
75+
assertEquals(BasicContentHandlerFactory.HANDLER_TYPE.IGNORE,
76+
BasicContentHandlerFactory.parseHandlerType("ignore", null));
77+
}
78+
79+
/** Unknown names throw with the valid list -- they must not fall back to the default. */
80+
@Test
81+
public void testParseHandlerTypeUnknownThrows() {
82+
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
83+
() -> BasicContentHandlerFactory.parseHandlerType("txet",
84+
BasicContentHandlerFactory.HANDLER_TYPE.XML));
85+
assertTrue(e.getMessage().contains("txet"), e.getMessage());
86+
assertTrue(e.getMessage().contains(BasicContentHandlerFactory.VALID_HANDLER_TYPE_NAMES),
87+
e.getMessage());
88+
}
89+
5790
public static void assertNotContains(String needle, byte[] hayStack)
5891
throws UnsupportedEncodingException {
5992
assertNotContains(needle, new String(hayStack, UTF_8));

tika-pipes/tika-pipes-plugins/tika-pipes-http/src/test/java/org/apache/tika/pipes/fetcher/http/HttpFetcherTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.apache.tika.pipes.fetcher.http;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
2021
import static org.junit.jupiter.api.Assertions.assertThrows;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
2123

2224
import java.io.ByteArrayOutputStream;
2325
import java.io.IOException;
@@ -284,6 +286,28 @@ FetcherManager getFetcherManager(String path) throws Exception {
284286
return FetcherManager.load(TikaPluginManager.load(tikaJsonConfig), tikaJsonConfig);
285287
}
286288

289+
/**
290+
* The one-line factory hand-off is what makes verifySsl real: deleting it silently
291+
* reverts to the factory's never-verify default this option was added to fix.
292+
*/
293+
@Test
294+
public void testVerifySslReachesClientFactory() throws Exception {
295+
HttpFetcher fetcher = (HttpFetcher) getFetcherManager("tika-config-http.json")
296+
.getFetcher("http-fetcher-1");
297+
assertTrue(fetcher.getHttpFetcherConfig().isVerifySsl(), "config default must be verify-on");
298+
assertTrue(new HttpFetcherConfig().isVerifySsl(), "bare config default must be verify-on");
299+
300+
HttpClientFactory factory = new HttpClientFactory();
301+
assertFalse(factory.isVerifySsl(), "the bare factory defaults to no-verify");
302+
fetcher.setHttpClientFactory(factory);
303+
fetcher.initialize();
304+
assertTrue(factory.isVerifySsl(), "the config default must reach the factory");
305+
306+
fetcher.getHttpFetcherConfig().setVerifySsl(false);
307+
fetcher.initialize();
308+
assertFalse(factory.isVerifySsl(), "the explicit opt-out must reach the factory");
309+
}
310+
287311
private void mockClientResponse(final HttpResponse response) throws Exception {
288312
httpFetcher = (HttpFetcher) getFetcherManager("tika-config-http.json").getFetcher("http-fetcher-1");
289313

tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/StackTraceTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@
1919

2020
import static org.junit.jupiter.api.Assertions.assertEquals;
2121
import static org.junit.jupiter.api.Assertions.assertNotNull;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
2223

2324
import java.io.ByteArrayInputStream;
2425
import java.io.IOException;
2526
import java.io.InputStream;
2627
import java.nio.charset.StandardCharsets;
28+
import java.nio.file.Files;
2729
import java.nio.file.Path;
2830
import java.nio.file.Paths;
2931
import java.util.ArrayList;
3032
import java.util.HashMap;
3133
import java.util.List;
3234
import java.util.Map;
35+
import java.util.stream.Stream;
3336

3437
import com.fasterxml.jackson.databind.JsonNode;
3538
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -136,6 +139,14 @@ public void testEncrypted() throws Exception {
136139
assertEquals(UNPROCESSEABLE, response.getStatus(), "unprocessable: /unpack");
137140
msg = getStringFromInputStream((InputStream) response.getEntity());
138141
assertContains("org.apache.tika.exception.EncryptedDocumentException", msg);
142+
143+
// The failed unpack must not orphan its zip in the emitter dir (handedOff cleanup).
144+
try (Stream<Path> files = Files.list(unpackTempDir)) {
145+
List<Path> zips = files
146+
.filter(p -> p.getFileName().toString().endsWith(".zip"))
147+
.toList();
148+
assertTrue(zips.isEmpty(), "orphaned unpack zips: " + zips);
149+
}
139150
}
140151

141152
@Test

tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaPipesTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,19 @@ private Response postTuple(FetchEmitTuple t) throws Exception {
336336
.post(writer.toString());
337337
}
338338

339+
/** Wiring pin: the reserved-id guard must actually run on /pipes, not just exist. */
340+
@Test
341+
public void testReservedFetcherIdIs400() throws Exception {
342+
FetchEmitTuple t = new FetchEmitTuple("reserved",
343+
new FetchKey(org.apache.tika.server.core.resource.PipesParsingHelper.DEFAULT_FETCHER_ID,
344+
"hello_world.xml"),
345+
new EmitKey(EMITTER_JSON_ID, ""), new Metadata());
346+
Response response = postTuple(t);
347+
assertEquals(400, response.getStatus());
348+
assertContains("reserved",
349+
getStringFromInputStream((InputStream) response.getEntity()));
350+
}
351+
339352
/** The /pipes body carries only status: a passback strategy would silently drop data. */
340353
@Test
341354
public void testPassbackStrategyIs400() throws Exception {

tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaResourceTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,71 @@ private String jsonContent(String path, String doc) throws Exception {
252252
.accept("application/json")
253253
.put(ClassLoader.getSystemResourceAsStream(doc));
254254
assertEquals(200, response.getStatus(), path + " should have succeeded");
255+
return contentOf(response);
256+
}
257+
258+
private static String contentOf(Response response) throws Exception {
255259
Metadata metadata = JsonMetadata.fromJson(new InputStreamReader(
256260
(InputStream) response.getEntity(), StandardCharsets.UTF_8));
257261
String content = metadata.get(TikaCoreProperties.TIKA_CONTENT);
258262
return content == null ? "" : content.trim();
259263
}
260264

265+
/** Bare /tika must return markdown -- provably distinct from plain text via the heading. */
266+
@Test
267+
public void testBareTikaIsMarkdownNotText() throws Exception {
268+
assertContains("# Chapter One", putRawContent("", TEST_HELLO_WORLD_HEADING));
269+
assertNotFound("# Chapter One", putRawContent("/text", TEST_HELLO_WORLD_HEADING));
270+
}
261271

272+
private String putRawContent(String pathSuffix, String doc) throws Exception {
273+
Response response = WebClient
274+
.create(endPoint + TIKA_PATH + pathSuffix)
275+
.put(ClassLoader.getSystemResourceAsStream(doc));
276+
assertEquals(200, response.getStatus(), pathSuffix + " should have succeeded");
277+
return getStringFromInputStream((InputStream) response.getEntity());
278+
}
279+
280+
/** POST /tika/config defaults to markdown; /config/text is body-only text. */
281+
@Test
282+
public void testConfigFamilyDefaults() throws Exception {
283+
assertContains("# Chapter One", postFileContent("/config", "text/plain"));
284+
assertNotFound("# Chapter One", postFileContent("/config/text", "text/plain"));
285+
}
286+
287+
/** The multipart JSON sibling endpoints, incl. the {handler} variant. */
288+
@Test
289+
public void testConfigJsonHandlerPlumbs() throws Exception {
290+
Response md = postFile("/config/json/md", "application/json");
291+
assertEquals(200, md.getStatus());
292+
assertContains("# Chapter One", contentOf(md));
293+
294+
Response text = postFile("/config/json/text", "application/json");
295+
assertEquals(200, text.getStatus());
296+
assertNotFound("# Chapter One", contentOf(text));
297+
298+
Response dflt = postFile("/config/json", "application/json");
299+
assertEquals(200, dflt.getStatus());
300+
assertContains("# Chapter One", contentOf(dflt));
301+
}
302+
303+
private Response postFile(String pathSuffix, String accept) {
304+
org.apache.cxf.jaxrs.ext.multipart.ContentDisposition cd =
305+
new org.apache.cxf.jaxrs.ext.multipart.ContentDisposition(
306+
"form-data; name=\"file\"; filename=\"hello.xml\"");
307+
org.apache.cxf.jaxrs.ext.multipart.Attachment att =
308+
new org.apache.cxf.jaxrs.ext.multipart.Attachment("file",
309+
ClassLoader.getSystemResourceAsStream(TEST_HELLO_WORLD_HEADING), cd);
310+
return WebClient
311+
.create(endPoint + TIKA_PATH + pathSuffix)
312+
.type("multipart/form-data")
313+
.accept(accept)
314+
.post(new org.apache.cxf.jaxrs.ext.multipart.MultipartBody(List.of(att)));
315+
}
316+
317+
private String postFileContent(String pathSuffix, String accept) throws Exception {
318+
Response response = postFile(pathSuffix, accept);
319+
assertEquals(200, response.getStatus(), pathSuffix + " should have succeeded");
320+
return getStringFromInputStream((InputStream) response.getEntity());
321+
}
262322
}

tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ public void testSupportedFetchersEmitters() throws Exception {
6060
assertTrue(config.isAllowPerRequestConfig());
6161
}
6262

63+
/** Documented defaults: 1 GiB request cap, 60s /async queue pause. */
64+
@Test
65+
public void testDocumentedDefaults() {
66+
TikaServerConfig config = new TikaServerConfig();
67+
assertEquals(1024L * 1024 * 1024, config.getMaxRequestSizeBytes());
68+
assertEquals(TikaServerConfig.DEFAULT_MAX_QUEUE_PAUSE_MILLIS, config.getMaxQueuePauseMillis());
69+
assertEquals(60000L, TikaServerConfig.DEFAULT_MAX_QUEUE_PAUSE_MILLIS);
70+
}
71+
6372
@Test
6473
public void testPorts() throws Exception {
6574
CommandLineParser parser = new DefaultParser();

tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerIntegrationTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static java.nio.charset.StandardCharsets.UTF_8;
2020
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
2122
import static org.junit.jupiter.api.Assertions.fail;
2223

2324
import java.io.IOException;
@@ -106,6 +107,24 @@ public void testBasicWithPipes() throws Exception {
106107
testBaseline();
107108
}
108109

110+
/**
111+
* Production wiring pin: BadRequestExceptionMapper must be registered in the real
112+
* server assembly, or 400 bodies are empty -- the CXF tests register it by hand,
113+
* so only a forked-server test can catch a dropped registration.
114+
*/
115+
@Test
116+
public void testBadRequestBodyReachesClient() throws Exception {
117+
startProcess(new String[]{"-config", getConfig("tika-config-server-basic.json")});
118+
awaitServerStartup();
119+
Response response = WebClient
120+
.create(endPoint + RMETA_PATH + "/txet")
121+
.accept("application/json")
122+
.put(ClassLoader.getSystemResourceAsStream(TEST_HELLO_WORLD));
123+
assertEquals(400, response.getStatus());
124+
String body = new String(((InputStream) response.getEntity()).readAllBytes(), UTF_8);
125+
assertTrue(body.contains("Valid types"), body);
126+
}
127+
109128
@Test
110129
public void testH2c() throws Exception {
111130
startProcess(new String[]{"-config", getConfig("tika-config-server-basic.json")});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.tika.server.core.resource;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNull;
21+
22+
import jakarta.ws.rs.core.HttpHeaders;
23+
import jakarta.ws.rs.core.Response;
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.apache.tika.pipes.api.PipesResult;
27+
28+
/**
29+
* Pins the documented status-code contract (migration guide, index.adoc) without
30+
* needing to saturate a real worker pool over HTTP.
31+
*/
32+
public class PipesResponseBuilderTest {
33+
34+
private static Response build(PipesResult.RESULT_STATUS status, long maxWaitMillis) {
35+
return PipesParsingHelper.responseBuilder(status, maxWaitMillis).build();
36+
}
37+
38+
private static String retryAfter(Response response) {
39+
return response.getHeaderString(HttpHeaders.RETRY_AFTER);
40+
}
41+
42+
@Test
43+
public void testPoolSaturationIs429WithWaitBasedRetryAfter() {
44+
Response response = build(PipesResult.RESULT_STATUS.CLIENT_UNAVAILABLE_WITHIN_MS, 30_000);
45+
assertEquals(429, response.getStatus());
46+
assertEquals("30", retryAfter(response));
47+
}
48+
49+
/** Sub-second waits must clamp to 1, not round down to a meaningless 0. */
50+
@Test
51+
public void testRetryAfterClampsToOneSecond() {
52+
Response response = build(PipesResult.RESULT_STATUS.CLIENT_UNAVAILABLE_WITHIN_MS, 100);
53+
assertEquals("1", retryAfter(response));
54+
}
55+
56+
@Test
57+
public void testCrashFamilyIs503WithShortRetryAfter() {
58+
for (PipesResult.RESULT_STATUS status : new PipesResult.RESULT_STATUS[]{
59+
PipesResult.RESULT_STATUS.TIMEOUT,
60+
PipesResult.RESULT_STATUS.OOM,
61+
PipesResult.RESULT_STATUS.UNSPECIFIED_CRASH}) {
62+
Response response = build(status, 30_000);
63+
assertEquals(503, response.getStatus(), status.name());
64+
assertEquals("5", retryAfter(response), status.name());
65+
}
66+
}
67+
68+
@Test
69+
public void testCallerErrorsAre400WithoutRetryAfter() {
70+
for (PipesResult.RESULT_STATUS status : new PipesResult.RESULT_STATUS[]{
71+
PipesResult.RESULT_STATUS.FETCHER_NOT_FOUND,
72+
PipesResult.RESULT_STATUS.EMITTER_NOT_FOUND}) {
73+
Response response = build(status, 30_000);
74+
assertEquals(400, response.getStatus(), status.name());
75+
assertNull(retryAfter(response), status.name());
76+
}
77+
}
78+
79+
@Test
80+
public void testIpcPayloadOverflowIs413() {
81+
assertEquals(413, build(PipesResult.RESULT_STATUS.PAYLOAD_LIMIT_EXCEEDED, 30_000).getStatus());
82+
}
83+
84+
@Test
85+
public void testSuccessIs200WithoutRetryAfter() {
86+
Response response = build(PipesResult.RESULT_STATUS.PARSE_SUCCESS, 30_000);
87+
assertEquals(200, response.getStatus());
88+
assertNull(retryAfter(response));
89+
}
90+
}

0 commit comments

Comments
 (0)