#!/usr/bin/env bash set -euo pipefail BASE_URL="${BASE_URL:-http://127.0.0.1:3000}" DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-smoke-script-device}" EMPLOYEE_IDENTIFIER="${EMPLOYEE_IDENTIFIER:-employee@hyinda.com}" EMPLOYEE_PASSWORD="${EMPLOYEE_PASSWORD:-EmployeePassword1234}" MANAGER_IDENTIFIER="${MANAGER_IDENTIFIER:-manager@hyinda.com}" MANAGER_PASSWORD="${MANAGER_PASSWORD:-ManagerPassword1234}" FINANCE_IDENTIFIER="${FINANCE_IDENTIFIER:-finance@hyinda.com}" FINANCE_PASSWORD="${FINANCE_PASSWORD:-FinancePassword1234}" SAMPLE_FILE="${SAMPLE_FILE:-/tmp/hyb-oa-sample-invoice.pdf}" if ! command -v curl >/dev/null 2>&1; then echo "[ERROR] curl not found" >&2 exit 1 fi if ! command -v jq >/dev/null 2>&1; then echo "[ERROR] jq not found" >&2 exit 1 fi TMP_DIR="$(mktemp -d)" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT if [[ ! -f "$SAMPLE_FILE" ]]; then REQUESTED_SAMPLE_FILE="$SAMPLE_FILE" SAMPLE_FILE="$TMP_DIR/hyb-oa-sample-invoice.pdf" printf '%%PDF-1.4\nhyb-oa generated smoke invoice\n' > "$SAMPLE_FILE" echo "[WARN] Sample file not found: $REQUESTED_SAMPLE_FILE; using generated smoke fixture" >&2 fi RUN_UPLOAD_FILE="$TMP_DIR/upload-$(basename "$SAMPLE_FILE")" cp "$SAMPLE_FILE" "$RUN_UPLOAD_FILE" printf '\n%%RUN-%s\n' "$(date +%s%N)" >> "$RUN_UPLOAD_FILE" extract_json_field() { local file_path="$1" local jq_expr="$2" jq -er "$jq_expr" "$file_path" } assert_status() { local actual="$1" local expected="$2" local name="$3" if [[ "$actual" != "$expected" ]]; then echo "[ERROR] $name expected HTTP $expected but got $actual" >&2 exit 1 fi } assert_error_code() { local file_path="$1" local expected_code="$2" local name="$3" local actual_code actual_code="$(jq -r '.error.code // empty' "$file_path")" if [[ "$actual_code" != "$expected_code" ]]; then echo "[ERROR] $name expected error code $expected_code but got $actual_code" >&2 cat "$file_path" >&2 exit 1 fi } echo "[1/13] Health check" health_body="$TMP_DIR/health.json" health_status="$(curl -sS -o "$health_body" -w "%{http_code}" "$BASE_URL/health")" assert_status "$health_status" "200" "health" echo "[2/13] Negative: no token should be 401" no_token_body="$TMP_DIR/no-token.json" no_token_status="$(curl -sS -o "$no_token_body" -w "%{http_code}" "$BASE_URL/api/reimbursements/test-id")" assert_status "$no_token_status" "401" "no token" echo "[3/13] Login employee" employee_login_body="$TMP_DIR/employee-login.json" employee_login_status="$(curl -sS -o "$employee_login_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/auth/login" \ -H "content-type: application/json" \ -H "x-device-fingerprint: $DEVICE_FINGERPRINT" \ -d "{\"identifier\":\"$EMPLOYEE_IDENTIFIER\",\"password\":\"$EMPLOYEE_PASSWORD\"}")" assert_status "$employee_login_status" "200" "employee login" EMPLOYEE_TOKEN="$(extract_json_field "$employee_login_body" '.data.accessToken')" echo "[4/13] Create reimbursement draft" create_body="$TMP_DIR/create.json" create_status="$(curl -sS -o "$create_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/reimbursements" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d '{}')" assert_status "$create_status" "200" "create reimbursement" REIMBURSEMENT_ID="$(extract_json_field "$create_body" '.data.id')" echo "[5/13] Upload invoice" upload_body="$TMP_DIR/upload.json" upload_status="$(curl -sS -o "$upload_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/invoice-pool/upload" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -F "file=@$RUN_UPLOAD_FILE")" assert_status "$upload_status" "200" "upload invoice" INVOICE_ID="$(extract_json_field "$upload_body" '.data.invoicePoolId')" echo "[5.1/13] Patch invoice total amount" patch_invoice_body="$TMP_DIR/patch-invoice.json" patch_invoice_status="$(curl -sS -o "$patch_invoice_body" -w "%{http_code}" \ -X PATCH "$BASE_URL/api/invoice-pool/$INVOICE_ID" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d '{"totalAmount":"188.80"}')" assert_status "$patch_invoice_status" "200" "patch invoice total amount" echo "[6/13] Bind invoice" bind_body="$TMP_DIR/bind.json" bind_status="$(curl -sS -o "$bind_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/invoice-pool/$INVOICE_ID/bind" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d "{\"reimbursementId\":\"$REIMBURSEMENT_ID\"}")" assert_status "$bind_status" "200" "bind invoice" echo "[7/13] Negative: bind same invoice again should be VALID_005" rebind_body="$TMP_DIR/rebind.json" rebind_status="$(curl -sS -o "$rebind_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/invoice-pool/$INVOICE_ID/bind" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d "{\"reimbursementId\":\"$REIMBURSEMENT_ID\"}")" assert_status "$rebind_status" "409" "rebind conflict" assert_error_code "$rebind_body" "VALID_005" "rebind conflict" echo "[8/13] Submit reimbursement" submit_body="$TMP_DIR/submit.json" submit_status="$(curl -sS -o "$submit_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/reimbursements/$REIMBURSEMENT_ID/submit" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d '{}')" assert_status "$submit_status" "200" "submit reimbursement" echo "[9/13] Login manager" manager_login_body="$TMP_DIR/manager-login.json" manager_login_status="$(curl -sS -o "$manager_login_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/auth/login" \ -H "content-type: application/json" \ -H "x-device-fingerprint: ${DEVICE_FINGERPRINT}-manager" \ -d "{\"identifier\":\"$MANAGER_IDENTIFIER\",\"password\":\"$MANAGER_PASSWORD\"}")" assert_status "$manager_login_status" "200" "manager login" MANAGER_TOKEN="$(extract_json_field "$manager_login_body" '.data.accessToken')" echo "[10/13] Login finance (approve permission, non-assignee)" finance_login_body="$TMP_DIR/finance-login.json" finance_login_status="$(curl -sS -o "$finance_login_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/auth/login" \ -H "content-type: application/json" \ -H "x-device-fingerprint: ${DEVICE_FINGERPRINT}-finance" \ -d "{\"identifier\":\"$FINANCE_IDENTIFIER\",\"password\":\"$FINANCE_PASSWORD\"}")" assert_status "$finance_login_status" "200" "finance login" FINANCE_TOKEN="$(extract_json_field "$finance_login_body" '.data.accessToken')" echo "[11/13] Query pending tasks as manager" tasks_body="$TMP_DIR/tasks.json" tasks_status="$(curl -sS -o "$tasks_body" -w "%{http_code}" \ -X GET "$BASE_URL/api/workflow/tasks?status=pending" \ -H "authorization: Bearer $MANAGER_TOKEN")" assert_status "$tasks_status" "200" "query workflow tasks" TASK_ID="$(jq -er --arg rid "$REIMBURSEMENT_ID" '.data.items[] | select(.workflow_instance.entity_id == $rid) | .id' "$tasks_body" | head -n 1)" if [[ -z "$TASK_ID" ]]; then echo "[ERROR] No pending task found for reimbursement $REIMBURSEMENT_ID" >&2 cat "$tasks_body" >&2 exit 1 fi echo "[12/13] Negative: finance cannot approve manager task (PERM_002)" finance_approve_body="$TMP_DIR/finance-approve.json" finance_approve_status="$(curl -sS -o "$finance_approve_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/workflow/tasks/$TASK_ID/approve" \ -H "authorization: Bearer $FINANCE_TOKEN" \ -H "content-type: application/json" \ -d '{}')" assert_status "$finance_approve_status" "403" "finance approve forbidden" assert_error_code "$finance_approve_body" "PERM_002" "finance approve forbidden" echo "[13/13] Approve by manager and fetch signed URL" manager_approve_body="$TMP_DIR/manager-approve.json" manager_approve_status="$(curl -sS -o "$manager_approve_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/workflow/tasks/$TASK_ID/approve" \ -H "authorization: Bearer $MANAGER_TOKEN" \ -H "content-type: application/json" \ -d '{}')" assert_status "$manager_approve_status" "200" "manager approve" signed_body="$TMP_DIR/signed-url.json" signed_status="$(curl -sS -o "$signed_body" -w "%{http_code}" \ -X POST "$BASE_URL/api/invoice-pool/$INVOICE_ID/signed-url" \ -H "authorization: Bearer $EMPLOYEE_TOKEN" \ -H "content-type: application/json" \ -d '{}')" assert_status "$signed_status" "200" "signed url" SIGNED_URL="$(extract_json_field "$signed_body" '.data.signedUrl')" signed_download="$TMP_DIR/signed-download.bin" download_status="$(curl -sS -L -o "$signed_download" -w "%{http_code}" "$SIGNED_URL")" assert_status "$download_status" "200" "signed url download" if [[ ! -s "$signed_download" ]]; then echo "[ERROR] Signed URL download is empty" >&2 exit 1 fi echo "[OK] Smoke test passed" echo "- reimbursementId: $REIMBURSEMENT_ID" echo "- invoiceId: $INVOICE_ID" echo "- taskId: $TASK_ID"