|
| 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