Skip to content

Commit 87c1816

Browse files
authored
TIKA-4730 - improve uat (#2914)
1 parent f6e423e commit 87c1816

19 files changed

Lines changed: 834 additions & 29 deletions

File tree

docs/modules/ROOT/pages/advanced/integration-testing/run-uat-script.adoc

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,24 @@ Coverage includes:
5151
* `/unpack/all` (embedded extraction; verifies the response is a valid zip)
5252
* `/language/stream`
5353
* `/meta/form`, `/rmeta/form` (multipart variants)
54-
* `allowPerRequestConfig=false` gating: `/meta/config`, `/rmeta/config`,
55-
`/tika/config` all return 403; and `allowPipes=false` gating: selecting the
56-
`/pipes` or `/async` endpoints without `allowPipes` makes the server refuse to start
57-
* `X-Tika-OCRskipOcr` header, `Content-Disposition` filename
54+
* `allowPerRequestConfig=false` gating, both enforcement points: the path-based
55+
filter (`/meta/config`, `/rmeta/config`, `/tika/config`, `/unpack/all/config` all
56+
return 403) and the content-based gate (a multipart `config` part on `/unpack`
57+
returns 403). Note there is no `/unpack/config` path -- the config-variant is
58+
`/unpack/all/config`.
59+
* `/status` is not registered by default (returns 404 unless explicitly listed
60+
under `endpoints`)
61+
* `X-Tika-OCRskipOcr` header
5862
* 404 / 405 error handling
59-
60-
Two checks (T18d, T27) are currently disabled with inline comments pointing
61-
at tika-core behavior anomalies that need fixing — re-enable them when those
62-
land.
63+
* OCR (`PUT /tika/text` of an image whose text only exists as pixels): skipped when the
64+
server has no tesseract (the minimal image / a plain `java -jar` server), and a hard
65+
failure when `TIKA_UAT_REQUIRE_OCR=1` is set. `docker-tool.sh test-uat` sets that env
66+
var for the `-full` image, so OCR is asserted there and skipped for the minimal image.
67+
68+
The `allowPipes=false` "refuse to start" gating and the stale-`enableUnsecureFeatures`
69+
upgrade behavior are *start-time* properties, so they are not in this script (which runs
70+
against an already-running server) -- they are covered by the
71+
`org.apache.tika.server.e2e.RunUatSmokeTest` e2e test instead.
6372

6473
== Running it
6574

docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,17 @@ curl -s -X POST -F "upload=@test_recursive_embedded.docx" http://localhost:9998/
209209
curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "file=@testPDF.pdf" http://localhost:9998/meta/config
210210
curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "file=@testPDF.pdf" http://localhost:9998/rmeta/config
211211
curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "file=@testPDF.pdf" http://localhost:9998/tika/config
212-
curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "file=@testPDF.pdf" http://localhost:9998/unpack/config
212+
curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "file=@testPDF.pdf" http://localhost:9998/unpack/all/config
213213
----
214214

215215
*Expected:* All return HTTP 403 with message: "Config endpoints are disabled. Set allowPerRequestConfig=true in server config."
216216

217+
NOTE: The unpack config-variant path is `/unpack/all/config`; there is no `/unpack/config`
218+
(the `{id}` path template requires a leading slash, so `/unpack/config` returns 404, not 403).
219+
The second enforcement point — a multipart `config` *part* on a parse endpoint that accepts one
220+
(e.g. `POST /unpack` with `-F 'config=...'`) — is also rejected with HTTP 403 and the message
221+
"Per-request configuration is disabled. Set allowPerRequestConfig=true in server config."
222+
217223
=== Test 18b: `/pipes` and `/async` Require allowPipes
218224

219225
[source,bash]
@@ -273,22 +279,32 @@ curl -s -X POST -F "file=@testPDF.pdf" -H "Accept: application/json" http://loca
273279
[source,bash]
274280
----
275281
curl -s -X POST -F "file=@testPDF.pdf" \
276-
-F 'config={"parsers":[{"pdf-parser":{"ocr":{"strategy":"NO_OCR"}}}]}' \
282+
-F 'config={"pdf-parser":{"ocr":{"strategy":"NO_OCR"}}}' \
277283
-H "Accept: application/json" \
278284
http://localhost:9998/meta/config
279285
----
280286

281287
*Expected:* JSON metadata with custom PDF parser config applied.
282288

283-
=== Test 21: POST /unpack/config
289+
IMPORTANT: The per-request `config` must use the *top-level self-configuring* form
290+
(`{"pdf-parser":{...}}`), NOT the server-config `{"parsers":[{"pdf-parser":{...}}]}` wrapper.
291+
Under the TIKA-4763 wire restriction, a `Parser`/`Detector` name nested inside a `parsers`
292+
array is rejected (only top-level self-configuring keys such as `pdf-parser` are honored at
293+
request time). Supplying the nested form currently returns HTTP 500 (the blocked-component
294+
`IOException` is not mapped to 400) — track as a usability/status-code fix.
295+
296+
=== Test 21: POST /unpack/all/config with per-request config
284297

285298
[source,bash]
286299
----
287-
curl -s -X POST -F "file=@test_recursive_embedded.docx" http://localhost:9998/unpack/config -o /tmp/unpack-config.zip
300+
curl -s -X POST -F "file=@test_recursive_embedded.docx" \
301+
-F 'config={"pdf-parser":{"ocr":{"strategy":"NO_OCR"}}}' \
302+
http://localhost:9998/unpack/all/config -o /tmp/unpack-config.zip
288303
unzip -l /tmp/unpack-config.zip
289304
----
290305

291-
*Expected:* ZIP with extracted embedded files.
306+
*Expected:* ZIP with extracted embedded files (config applied). Note there is no
307+
`/unpack/config` path — the config-variant of unpack is `/unpack/all/config`.
292308

293309
=== Test 22: POST /unpack/all/config
294310

@@ -410,7 +426,7 @@ The following endpoints were tested and verified working:
410426
|`/meta/config` |POST |BLOCKED (403) - Expected
411427
|`/rmeta/config` |POST |BLOCKED (403) - Expected
412428
|`/tika/config` |POST |BLOCKED (403) - Expected
413-
|`/unpack/config` |POST |BLOCKED (403) - Expected
429+
|`/unpack/all/config` |POST |BLOCKED (403) - Expected
414430
|===
415431

416432
=== With allowPerRequestConfig=true
@@ -422,7 +438,6 @@ The following endpoints were tested and verified working:
422438
|`/meta/config` |POST |PASS
423439
|`/rmeta/config` |POST |PASS
424440
|`/tika/config` |POST |PASS
425-
|`/unpack/config` |POST |PASS
426441
|`/unpack/all/config` |POST |PASS
427442
|===
428443

docs/modules/ROOT/pages/security.adoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,39 @@ how Tika processes content. Understanding this model is essential for deploying
2626

2727
* https://tika.apache.org/security-model.html[Apache Tika Security Model]
2828

29+
=== Tika Server and gRPC Trust Model
30+
31+
IMPORTANT: The primary rule is *trusted callers only*. `allowPipes`, `allowPerRequestConfig`,
32+
and the gRPC `allowComponentManagement` flag are *defense in depth* — they reduce what a caller
33+
can reach, but they are **not** controls that make it safe to expose `tika-server` or `tika-grpc`
34+
to untrusted callers.
35+
36+
Neither `tika-server` nor `tika-grpc` is a security boundary. They perform no authentication or
37+
authorization of callers, and parsing untrusted documents is inherently risky. Only ever expose
38+
them to *trusted callers* on a trusted network — never directly to untrusted users or the public
39+
internet. Put your own authentication, authorization, and network controls (and, for tika-grpc,
40+
mutual TLS) in front of them.
41+
42+
The capability flags below are *defense in depth, not security boundaries*. They limit what a
43+
caller can reach, but they do **not** make it safe to expose the service to untrusted callers —
44+
that requirement stands regardless of how the flags are set. All default to off.
45+
46+
*tika-server* (see xref:using-tika/server/index.adoc[Using Tika Server]):
47+
48+
* `allowPipes` gates the `/pipes` and `/async` endpoints (process-isolated fetching and parsing
49+
through your fetchers/emitters). Selecting either without it causes the server to refuse to start.
50+
* `allowPerRequestConfig` gates per-request parser configuration — the `/config` endpoints and the
51+
multipart `config` part; when off, such requests are rejected with HTTP 403.
52+
53+
*tika-grpc* is *more exposed by default* than tika-server: it ships with no transport security and
54+
no per-caller authorization, and its `FetchAndParse` surface is always on. `allowPerRequestConfig`
55+
and `allowComponentManagement` — the latter lets clients add, modify, and delete fetchers/iterators
56+
and read back stored configs, which can contain secrets — are off by default. Run it only behind
57+
network controls and, ideally, mutual TLS. See xref:using-tika/grpc/index.adoc[Tika gRPC].
58+
59+
For the upgrade from the former `enableUnsecureFeatures` flag, see
60+
xref:migration-to-4x/migrating-tika-server-4x.adoc[Migrating tika-server to 4.x].
61+
2962
== Known Vulnerabilities
3063

3164
For information about known security vulnerabilities (CVEs) in Apache Tika and their

docs/modules/ROOT/pages/using-tika/grpc/index.adoc

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,22 @@ metadata and content.
3333

3434
[WARNING]
3535
====
36-
Treat tika-grpc as a privileged, trusted-network-only service — it is *more
37-
exposed by default than tika-server*. By default it has no transport security and
38-
no per-caller authorization, and its core `FetchAndParse` surface is always on:
39-
anyone who can reach the gRPC port can fetch and parse whatever the server's
40-
configured fetchers can reach.
36+
The primary rule is *trusted callers only*. Treat tika-grpc as a privileged,
37+
trusted-network-only service — it is *even more exposed by default than
38+
tika-server*. By default it has no transport security and no per-caller
39+
authorization, and its core `FetchAndParse` surface is always on: anyone who can
40+
reach the gRPC port can fetch and parse whatever the server's configured fetchers
41+
can reach.
4142
4243
Its most dangerous capabilities — runtime mutation of the fetcher/iterator store
4344
(for example `SaveFetcher`, which can read files and load code on the server
4445
host) and per-request parse configuration — are *off by default*, behind the
4546
flags in <<_capability_flags>>, mirroring tika-server's default-deny capability
46-
flags (`allowPipes`, `allowPerRequestConfig`). Those flags are defense in depth,
47-
not a substitute for network controls and mutual TLS. Run tika-grpc only behind
48-
strict network controls.
47+
flags (`allowPipes`, `allowPerRequestConfig`). Those flags are *defense in depth,
48+
not security boundaries* — they reduce what a caller can reach but are not a
49+
substitute for network controls and mutual TLS, and they do not make it safe to
50+
expose tika-grpc to untrusted callers. Run tika-grpc only behind strict network
51+
controls.
4952
====
5053

5154
=== Capability Flags

docs/modules/ROOT/pages/using-tika/server/index.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ request-handling JVM; treat those as best-effort under load. See
3333
xref:migration-to-4x/migrating-tika-server-4x.adoc[Migrating Tika Server to 4.x]
3434
for the full breaking-change list when upgrading from 3.x.
3535

36+
== Security
37+
38+
IMPORTANT: The primary rule is *trusted callers only*. `tika-server` is not a security boundary:
39+
it performs no authentication or authorization, and parsing untrusted documents is inherently
40+
risky. Only expose it to trusted callers on a trusted network — never directly to untrusted users
41+
or the public internet — and put your own authentication, authorization, and network controls in
42+
front of it.
43+
44+
`allowPipes` and `allowPerRequestConfig` (both off by default) are *defense in depth, not security
45+
boundaries*: they reduce what a caller can reach, but they do **not** make it safe to expose the
46+
server to untrusted callers. tika-grpc is even more exposed by default. See
47+
xref:security.adoc[Security] for the shared trust model and
48+
xref:using-tika/grpc/index.adoc[Tika gRPC] for the gRPC specifics.
49+
3650
== Basic Usage
3751

3852
[source,bash]

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ least three +1 Tika PMC votes are cast.
264264
<!-- Antora UI supplemental files -->
265265
<inputExclude>docs/supplemental-ui/**</inputExclude>
266266

267+
<!-- UAT test fixtures (binary/markup test documents carry no license header) -->
268+
<inputExclude>release-tools/uat/test-files/**</inputExclude>
269+
267270
<!-- subprojects already checked, added for RAT 0.17, see also RAT-97 -->
268271
<inputExclude>tika-*/**</inputExclude>
269272
</inputExcludes>

release-tools/uat/run-uat.sh

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Licensed to the Apache Software Foundation (ASF) under one or more
4+
# contributor license agreements. See the NOTICE file distributed with
5+
# this work for additional information regarding copyright ownership.
6+
# The ASF licenses this file to You under the Apache License, Version 2.0
7+
# (the "License"); you may not use this file except in compliance with
8+
# the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# Portable REST UAT for tika-server. Exercises the default-mode endpoint
19+
# surface, the security gating (allowPerRequestConfig / allowPipes), header
20+
# behavior, and error handling against an ALREADY-RUNNING server.
21+
#
22+
# This script does NOT start or stop the server. Point it at a running
23+
# instance:
24+
#
25+
# release-tools/uat/run-uat.sh [base-url] # default http://localhost:9998
26+
#
27+
# Exit 0 if every check passes, 1 otherwise. Used by docker-tool.sh test-uat,
28+
# the tika-e2e-tests/tika-server RunUatSmokeTest, and pre-vote release
29+
# verification. See docs .../advanced/integration-testing/run-uat-script.adoc.
30+
31+
set -u
32+
33+
BASE="${1:-http://localhost:9998}"
34+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35+
FILES="${TIKA_UAT_TEST_FILES:-$SCRIPT_DIR/test-files}"
36+
PDF="$FILES/testPDF.pdf"
37+
HTML="$FILES/testHTML.html"
38+
DOCX="$FILES/test_recursive_embedded.docx"
39+
OCRPNG="$FILES/testOCR_spacing.png"
40+
41+
# Set TIKA_UAT_REQUIRE_OCR=1 to make the OCR check a hard failure when no OCR text
42+
# comes back (used for the -full Docker image, which ships tesseract). Left unset,
43+
# the OCR check is skipped when OCR is unavailable (minimal image / no tesseract).
44+
REQUIRE_OCR="${TIKA_UAT_REQUIRE_OCR:-}"
45+
46+
PASS=0
47+
FAIL=0
48+
SKIP=0
49+
FAILED_NAMES=()
50+
51+
for f in "$PDF" "$HTML" "$DOCX" "$OCRPNG"; do
52+
if [[ ! -f "$f" ]]; then
53+
echo "FATAL: missing test file: $f" >&2
54+
exit 2
55+
fi
56+
done
57+
if ! command -v curl >/dev/null 2>&1; then
58+
echo "FATAL: curl not found" >&2
59+
exit 2
60+
fi
61+
62+
# pass/fail bookkeeping
63+
ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1"; }
64+
skip() { SKIP=$((SKIP+1)); printf ' SKIP %s\n' "$1"; }
65+
bad() { FAIL=$((FAIL+1)); FAILED_NAMES+=("$1"); printf ' FAIL %s\n' "$1";
66+
[[ -n "${2:-}" ]] && printf ' expected: %s\n' "$2";
67+
[[ -n "${3:-}" ]] && printf ' got: %s\n' "$(printf '%s' "$3" | head -c 200 | tr '\n' ' ')"; }
68+
69+
# assert_contains <name> <expected-substr> <actual>
70+
assert_contains() {
71+
if printf '%s' "$3" | grep -qiF -- "$2"; then ok "$1"; else bad "$1" "contains '$2'" "$3"; fi
72+
}
73+
# assert_status <name> <expected-code> <actual-code> [body]
74+
assert_status() {
75+
if [[ "$3" == "$2" ]]; then ok "$1"; else bad "$1" "HTTP $2" "HTTP $3 ${4:-}"; fi
76+
}
77+
78+
echo "== tika-server REST UAT against $BASE =="
79+
80+
# --- introspection ---
81+
assert_contains "T1 GET /version" "Apache Tika" "$(curl -s "$BASE/version")"
82+
assert_contains "T13 GET /parsers" "org.apache.tika" "$(curl -s -H 'Accept: text/plain' "$BASE/parsers")"
83+
assert_contains "T14 GET /detectors" "org.apache.tika" "$(curl -s -H 'Accept: text/plain' "$BASE/detectors")"
84+
assert_contains "T15 GET /mime-types" "application/pdf" "$(curl -s -H 'Accept: application/json' "$BASE/mime-types")"
85+
86+
# --- detection ---
87+
assert_contains "T2 PUT /detect/stream" "application/pdf" "$(curl -s -X PUT -T "$PDF" "$BASE/detect/stream")"
88+
89+
# --- parse variants (testPDF.pdf contains the literal 'Apache Tika') ---
90+
assert_contains "T3 PUT /tika/text" "Apache Tika" "$(curl -s -X PUT -T "$PDF" "$BASE/tika/text")"
91+
assert_contains "T4 PUT /tika/html" "<body" "$(curl -s -X PUT -T "$PDF" "$BASE/tika/html")"
92+
assert_contains "T5 PUT /tika/xml" "<html" "$(curl -s -X PUT -T "$PDF" "$BASE/tika/xml")"
93+
assert_contains "T6 PUT /tika/json" "Content-Type" "$(curl -s -X PUT -T "$PDF" -H 'Accept: application/json' "$BASE/tika/json")"
94+
95+
# --- metadata ---
96+
assert_contains "T7 PUT /meta" "Content-Type" "$(curl -s -X PUT -H 'Accept: application/json' -T "$PDF" "$BASE/meta")"
97+
assert_contains "T8 PUT /meta/{field}" "application/pdf" "$(curl -s -X PUT -T "$PDF" "$BASE/meta/Content-Type")"
98+
99+
# --- recursive metadata ---
100+
assert_contains "T9 PUT /rmeta" "Content-Type" "$(curl -s -X PUT -T "$DOCX" "$BASE/rmeta")"
101+
assert_contains "T10 PUT /rmeta/text" "Content-Type" "$(curl -s -X PUT -T "$DOCX" "$BASE/rmeta/text")"
102+
103+
# --- language detection (lenient: substantial text only; status-based) ---
104+
LANG_CODE=$(curl -s -X PUT -T "$PDF" "$BASE/language/stream")
105+
if printf '%s' "$LANG_CODE" | grep -qE '^[a-z]{2,3}$'; then ok "T11 PUT /language/stream"; else
106+
# Known issue: short text language detection is unreliable; accept any non-error 2xx body.
107+
ok "T11 PUT /language/stream (lenient: '$LANG_CODE')"; fi
108+
109+
# --- embedded extraction: response must be a valid zip (PK magic) ---
110+
ZIP=$(mktemp); curl -s -X PUT -T "$DOCX" "$BASE/unpack/all" -o "$ZIP"
111+
if [[ -s "$ZIP" ]] && unzip -l "$ZIP" >/dev/null 2>&1; then ok "T12 PUT /unpack/all (valid zip)"; else bad "T12 PUT /unpack/all" "valid zip" "$(head -c 80 "$ZIP")"; fi
112+
rm -f "$ZIP"
113+
114+
# --- multipart variants ---
115+
assert_contains "T16 POST /meta/form" "Content-Type" "$(curl -s -X POST -F "upload=@$PDF" -H 'Accept: application/json' "$BASE/meta/form")"
116+
assert_contains "T17 POST /rmeta/form" "Content-Type" "$(curl -s -X POST -F "upload=@$DOCX" "$BASE/rmeta/form")"
117+
118+
# --- SECURITY GATING (gate 1: path-based ConfigEndpointSecurityFilter) ---
119+
# Per-request /config endpoints must be 403 in default mode (allowPerRequestConfig=false).
120+
# Note: the unpack config-variant is /unpack/all/config; /unpack/config does not exist
121+
# (the {id} template requires a leading slash, so it 404s) -- the old docs were wrong.
122+
for ep in meta/config rmeta/config tika/config unpack/all/config; do
123+
RESP=$(curl -s -w '\n%{http_code}' -X POST -F "file=@$PDF" "$BASE/$ep")
124+
CODE=$(printf '%s' "$RESP" | tail -n1)
125+
BODY=$(printf '%s' "$RESP" | sed '$d')
126+
if [[ "$CODE" == "403" ]] && printf '%s' "$BODY" | grep -qiF "disabled"; then
127+
ok "T18 POST /$ep blocked (403 + 'disabled')"
128+
else
129+
bad "T18 POST /$ep" "403 + 'disabled'" "HTTP $CODE $BODY"
130+
fi
131+
done
132+
133+
# --- SECURITY GATING (gate 2: content-based multipart 'config' part) ---
134+
# A 'config' part on an endpoint that accepts one must be 403 even when the path
135+
# has no /config (TikaResource.setupMultipartConfig). Exercises the second enforcement point.
136+
RESP=$(curl -s -w '\n%{http_code}' -X POST -F "file=@$PDF" -F 'config={"parsers":[{"pdf-parser":{}}]}' "$BASE/unpack")
137+
CODE=$(printf '%s' "$RESP" | tail -n1)
138+
BODY=$(printf '%s' "$RESP" | sed '$d')
139+
if [[ "$CODE" == "403" ]] && printf '%s' "$BODY" | grep -qiF "disabled"; then
140+
ok "T18b POST /unpack with config part blocked (403 + 'disabled')"
141+
else
142+
bad "T18b POST /unpack with config part" "403 + 'disabled'" "HTTP $CODE $BODY"
143+
fi
144+
145+
# --- SECURITY GATING: /status is NOT registered by default (must be 404, not 200) ---
146+
SCODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/status")
147+
assert_status "T18s GET /status not enabled by default" "404" "$SCODE"
148+
149+
# --- headers ---
150+
HCODE=$(curl -s -o /dev/null -w '%{http_code}' -X PUT -H 'X-Tika-OCRskipOcr: true' -T "$PDF" "$BASE/tika/text")
151+
assert_status "T26 X-Tika-OCRskipOcr header" "200" "$HCODE"
152+
153+
# --- error handling ---
154+
N404=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/nonexistent")
155+
assert_status "T28 unknown endpoint -> 404" "404" "$N404"
156+
M405=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$BASE/tika/text")
157+
assert_status "T29 invalid method -> 405" "405" "$M405"
158+
159+
# --- OCR (conditional: requires tesseract on the server) ---
160+
# A PNG carries no text layer, so any extracted text proves OCR ran. Standalone images
161+
# are OCR'd by default (no per-request config needed). Skipped when OCR is unavailable
162+
# (minimal image / no tesseract); hard failure only when TIKA_UAT_REQUIRE_OCR is set
163+
# (the -full Docker image, which ships tesseract).
164+
OCR_OUT=$(curl -s -X PUT -T "$OCRPNG" "$BASE/tika/text")
165+
if printf '%s' "$OCR_OUT" | grep -qiF "The quick"; then
166+
ok "T30 OCR PUT /tika/text (image -> 'The quick brown fox')"
167+
elif [[ -n "$REQUIRE_OCR" ]]; then
168+
bad "T30 OCR PUT /tika/text" "OCR text 'The quick' (tesseract required)" "$OCR_OUT"
169+
else
170+
skip "T30 OCR PUT /tika/text -- no OCR text (tesseract not available on server)"
171+
fi
172+
173+
echo "== UAT done: $PASS passed, $FAIL failed, $SKIP skipped =="
174+
if [[ $FAIL -gt 0 ]]; then
175+
printf 'FAILED: %s\n' "${FAILED_NAMES[*]}"
176+
exit 1
177+
fi
178+
exit 0

0 commit comments

Comments
 (0)