#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REQUEST_DIR="${SCRIPT_DIR}/../src/test/resources/requests" VALID_REQUEST="${REQUEST_DIR}/exam-sprint-achievement-report-request.json" INVALID_REQUEST="${REQUEST_DIR}/exam-sprint-achievement-report-invalid-request.json" BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" OUTPUT_PATH="${OUTPUT_PATH:-${PWD}/achievement-report-demo.pdf}" HTML_OUTPUT_PATH="${HTML_OUTPUT_PATH:-${PWD}/achievement-report-preview.html}" MAX_ATTEMPTS="${MAX_ATTEMPTS:-30}" POLL_INTERVAL_SECONDS="${POLL_INTERVAL_SECONDS:-1}" JSON_PARSER="" TMP_FILES=() usage() { cat <&2 exit 1 } make_temp_file() { local file file="$(mktemp)" TMP_FILES+=("$file") printf '%s\n' "$file" } require_command() { command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" } select_json_parser() { if command -v jq >/dev/null 2>&1; then JSON_PARSER="jq" elif command -v python3 >/dev/null 2>&1; then JSON_PARSER="python3" else die "JSON parsing requires jq or python3, but neither was found in PATH." fi } json_get() { local file="$1" local path="$2" if [ "$JSON_PARSER" = "jq" ]; then jq -er ".$path" "$file" else python3 - "$file" "$path" <<'PY' import json import sys from pathlib import Path file_path = Path(sys.argv[1]) path = [part for part in sys.argv[2].split('.') if part] value = json.loads(file_path.read_text(encoding='utf-8')) for part in path: if isinstance(value, dict) and part in value: value = value[part] else: sys.exit(1) if value is None: sys.exit(1) if isinstance(value, (dict, list)): print(json.dumps(value, ensure_ascii=False)) else: print(value) PY fi } pretty_print_json() { local file="$1" if [ "$JSON_PARSER" = "jq" ]; then jq . "$file" else python3 - "$file" <<'PY' import json import sys from pathlib import Path file_path = Path(sys.argv[1]) data = json.loads(file_path.read_text(encoding='utf-8')) print(json.dumps(data, ensure_ascii=False, indent=2)) PY fi } normalize_url() { local url="$1" if [[ "$url" =~ ^https?:// ]]; then printf '%s\n' "$url" elif [[ "$url" == /* ]]; then printf '%s%s\n' "${BASE_URL%/}" "$url" else printf '%s/%s\n' "${BASE_URL%/}" "$url" fi } post_json() { local url="$1" local payload="$2" local output="$3" curl -sS -o "$output" -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data "@$payload" "$url" } get_json() { local url="$1" local output="$2" curl -sS -o "$output" -w '%{http_code}' "$url" } download_file() { local url="$1" local output="$2" curl -sS -L -o "$output" -w '%{http_code}' "$url" } ensure_request_files() { [ -f "$VALID_REQUEST" ] || die "Missing request fixture: $VALID_REQUEST" [ -f "$INVALID_REQUEST" ] || die "Missing request fixture: $INVALID_REQUEST" } run_success_mode() { local submit_url submit_body submit_status report_id query_url query_body query_status current_status local download_url download_target download_status preview_html_url preview_html_target preview_html_status attempt submit_url="${BASE_URL%/}/api/exam-sprint/reports" submit_body="$(make_temp_file)" submit_status="$(post_json "$submit_url" "$VALID_REQUEST" "$submit_body")" || die "Failed to submit request to ${submit_url}" if [ "$submit_status" != "202" ]; then printf 'Submit request failed with HTTP %s\n' "$submit_status" >&2 pretty_print_json "$submit_body" >&2 exit 1 fi report_id="$(json_get "$submit_body" 'data.reportId')" || die "Accepted response did not contain data.reportId" printf 'Report created: %s\n' "$report_id" query_url="${BASE_URL%/}/api/exam-sprint/reports/${report_id}" query_body="$(make_temp_file)" for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do query_status="$(get_json "$query_url" "$query_body")" || die "Failed to query report status from ${query_url}" if [ "$query_status" != "200" ]; then printf 'Query report failed with HTTP %s\n' "$query_status" >&2 pretty_print_json "$query_body" >&2 exit 1 fi current_status="$(json_get "$query_body" 'data.generationStatus')" || die "Report response did not contain data.generationStatus" printf 'Poll %d/%s: %s\n' "$attempt" "$MAX_ATTEMPTS" "$current_status" case "$current_status" in SUCCESS) download_url="$(json_get "$query_body" 'data.downloadUrl')" || die "Successful report did not contain data.downloadUrl" download_url="$(normalize_url "$download_url")" download_target="$(make_temp_file)" download_status="$(download_file "$download_url" "$download_target")" || die "Failed to download PDF from ${download_url}" if [ "$download_status" != "200" ]; then printf 'Download failed with HTTP %s\n' "$download_status" >&2 pretty_print_json "$download_target" >&2 exit 1 fi preview_html_url="$(json_get "$query_body" 'data.previewHtmlUrl')" || die "Successful report did not contain data.previewHtmlUrl" preview_html_url="$(normalize_url "$preview_html_url")" preview_html_target="$(make_temp_file)" preview_html_status="$(download_file "$preview_html_url" "$preview_html_target")" || die "Failed to download preview HTML from ${preview_html_url}" if [ "$preview_html_status" != "200" ]; then printf 'Preview HTML download failed with HTTP %s\n' "$preview_html_status" >&2 pretty_print_json "$preview_html_target" >&2 exit 1 fi mv "$download_target" "$OUTPUT_PATH" mv "$preview_html_target" "$HTML_OUTPUT_PATH" printf 'PDF downloaded to: %s\n' "$OUTPUT_PATH" printf 'Preview HTML downloaded to: %s\n' "$HTML_OUTPUT_PATH" return 0 ;; FAILED|EXPIRED) printf 'Report ended with status %s\n' "$current_status" >&2 pretty_print_json "$query_body" >&2 exit 1 ;; esac sleep "$POLL_INTERVAL_SECONDS" done printf 'Report did not reach SUCCESS within %s attempts\n' "$MAX_ATTEMPTS" >&2 pretty_print_json "$query_body" >&2 exit 1 } run_invalid_mode() { local submit_url response_body response_status submit_url="${BASE_URL%/}/api/exam-sprint/reports" response_body="$(make_temp_file)" response_status="$(post_json "$submit_url" "$INVALID_REQUEST" "$response_body")" || die "Failed to submit invalid request to ${submit_url}" printf 'HTTP %s\n' "$response_status" pretty_print_json "$response_body" if [ "$response_status" != "400" ]; then die "Expected HTTP 400 for invalid request, got ${response_status}" fi } main() { local mode="${1:-}" if [ "$#" -gt 1 ]; then usage exit 1 fi case "$mode" in ""|--help|-h|help) usage ;; success) require_command curl select_json_parser ensure_request_files run_success_mode ;; invalid) require_command curl select_json_parser ensure_request_files run_invalid_mode ;; *) usage exit 1 ;; esac } main "$@"