#!/usr/bin/env bash set -euo pipefail BASE_URL="${BASE_URL:-http://127.0.0.1:3000}" DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-regression-dashboard}" FINANCE_MGR_IDENTIFIER="${FINANCE_MGR_IDENTIFIER:-finance_mgr@hyinda.com}" FINANCE_MGR_PASSWORD="${FINANCE_MGR_PASSWORD:-FinanceMgrPassword1234}" DASHBOARD_PERF_CHECK="${DASHBOARD_PERF_CHECK:-0}" 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 assert_status() { local actual="$1" local expected="$2" local name="$3" local body_file="${4:-}" if [[ "$actual" != "$expected" ]]; then echo "[ERROR] $name expected HTTP $expected but got $actual" >&2 if [[ -n "$body_file" && -f "$body_file" ]]; then cat "$body_file" >&2 fi exit 1 fi } login() { local out_file="$1" local status status="$(curl -sS -o "$out_file" -w "%{http_code}" \ -X POST "$BASE_URL/api/auth/login" \ -H "content-type: application/json" \ -H "x-device-fingerprint: $DEVICE_FINGERPRINT" \ -d "{\"identifier\":\"$FINANCE_MGR_IDENTIFIER\",\"password\":\"$FINANCE_MGR_PASSWORD\"}")" assert_status "$status" "200" "login" "$out_file" jq -er '.data.accessToken' "$out_file" } api_json() { local method="$1" local url="$2" local token="$3" local out_file="$4" curl -sS -o "$out_file" -w "%{http_code}" \ -X "$method" "$url" \ -H "authorization: Bearer $token" } assert_json_shape() { local file="$1" local expr="$2" local name="$3" if ! jq -e "$expr" "$file" >/dev/null; then echo "[ERROR] $name json assertion failed: $expr" >&2 cat "$file" >&2 exit 1 fi } echo "[1/5] Login" TOKEN="$(login "$TMP_DIR/login.json")" FILTER_QS="dateFrom=2026-01-01&dateTo=2026-12-31" echo "[2/5] Query dashboard endpoints" status="$(api_json GET "$BASE_URL/api/dashboard/expense-overview?$FILTER_QS" "$TOKEN" "$TMP_DIR/overview.json")" assert_status "$status" "200" "dashboard overview" "$TMP_DIR/overview.json" assert_json_shape "$TMP_DIR/overview.json" '.data.totalCount >= 0 and (.data.totalAmount | type == "string")' "overview" status="$(api_json GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" "$TOKEN" "$TMP_DIR/trends-1.json")" assert_status "$status" "200" "dashboard trends(1)" "$TMP_DIR/trends-1.json" assert_json_shape "$TMP_DIR/trends-1.json" '.data.points | type == "array"' "trends-array" assert_json_shape "$TMP_DIR/trends-1.json" '.data.points | all(.[]; (.date | type == "string") and (.amount | type == "string") and (.count | type == "number"))' "trends-shape" assert_json_shape "$TMP_DIR/trends-1.json" ' .data.points | reduce .[] as $p ({prev:null, ok:true}; if .ok == false then . elif .prev == null or $p.date >= .prev then {prev: $p.date, ok: true} else {prev: $p.date, ok: false} end ) | .ok ' "trends-order" status="$(api_json GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" "$TOKEN" "$TMP_DIR/trends-2.json")" assert_status "$status" "200" "dashboard trends(2)" "$TMP_DIR/trends-2.json" if ! diff -u <(jq -S '.data.points' "$TMP_DIR/trends-1.json") <(jq -S '.data.points' "$TMP_DIR/trends-2.json") >/dev/null; then echo "[ERROR] trends response not stable across repeated calls" >&2 exit 1 fi status="$(api_json GET "$BASE_URL/api/dashboard/top-categories?$FILTER_QS&limit=5" "$TOKEN" "$TMP_DIR/top-categories.json")" assert_status "$status" "200" "dashboard top-categories" "$TMP_DIR/top-categories.json" assert_json_shape "$TMP_DIR/top-categories.json" '.data.items | type == "array" and (length <= 5)' "top-categories-array" assert_json_shape "$TMP_DIR/top-categories.json" '.data.items | all(.[]; (.code | type == "string") and (.name | type == "string") and (.amount | type == "string") and (.count | type == "number"))' "top-categories-shape" status="$(api_json GET "$BASE_URL/api/dashboard/approval-efficiency?$FILTER_QS" "$TOKEN" "$TMP_DIR/efficiency.json")" assert_status "$status" "200" "dashboard approval-efficiency" "$TMP_DIR/efficiency.json" assert_json_shape "$TMP_DIR/efficiency.json" '.data.averageHours >= 0 and .data.approvedCount >= 0' "efficiency" echo "[3/5] Export dashboard data" status="$(curl -sS -o "$TMP_DIR/export.csv" -w "%{http_code}" \ -X GET "$BASE_URL/api/exports/dashboard/reimbursements?format=csv&$FILTER_QS" \ -H "authorization: Bearer $TOKEN")" assert_status "$status" "200" "export dashboard csv" "$TMP_DIR/export.csv" if [[ ! -s "$TMP_DIR/export.csv" ]]; then echo "[ERROR] dashboard csv export is empty" >&2 exit 1 fi status="$(curl -sS -o "$TMP_DIR/export.xlsx" -w "%{http_code}" \ -X GET "$BASE_URL/api/exports/dashboard/reimbursements?format=xlsx&$FILTER_QS" \ -H "authorization: Bearer $TOKEN")" assert_status "$status" "200" "export dashboard xlsx" "$TMP_DIR/export.xlsx" if [[ ! -s "$TMP_DIR/export.xlsx" ]]; then echo "[ERROR] dashboard xlsx export is empty" >&2 exit 1 fi if [[ "$DASHBOARD_PERF_CHECK" == "1" ]]; then echo "[4/5] Optional performance probe" PERF_TIME="$(curl -sS -o /dev/null -w "%{time_total}" \ -X GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" \ -H "authorization: Bearer $TOKEN")" echo "[INFO] trends endpoint time_total=${PERF_TIME}s" else echo "[4/5] Optional performance probe (skipped, DASHBOARD_PERF_CHECK=$DASHBOARD_PERF_CHECK)" fi echo "[5/5] Done" echo "[OK] Dashboard regression passed"