- Add Dockerfile, docker-entrypoint.sh, and .dockerignore for containerized deployment - Fix middleware to exclude /api/tenants/signup from auth (P0 signup bug) - Add Playwright E2E tests (16 browser tests) and curl-based API test script (80 tests) - Add playwright config and dev dependency - Update .gitignore with proper exclusions - Add v1 milestone audit report and ISP system PRD Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1244 lines
37 KiB
Bash
1244 lines
37 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# NetForge v1.0 — Comprehensive UAT Test Script
|
|
# =============================================================================
|
|
# Tests all API endpoints and critical workflows end-to-end via curl.
|
|
# Requires: Docker containers running (docker compose up)
|
|
#
|
|
# Usage: bash scripts/uat-test.sh
|
|
# =============================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
BASE_URL="http://localhost:3000"
|
|
COOKIE_JAR="/tmp/nf_uat_cookies.txt"
|
|
COOKIE_JAR_SUPER="/tmp/nf_uat_cookies_super.txt"
|
|
COOKIE_JAR_TENANT2="/tmp/nf_uat_cookies_t2.txt"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
SKIP=0
|
|
FAILURES=""
|
|
|
|
# Unique suffix per run to avoid duplicate-name errors
|
|
RUN_ID=$(date +%s | tail -c 6)
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
log_section() {
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${BOLD} $1${NC}"
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
}
|
|
|
|
log_test() {
|
|
echo -ne " ${BOLD}TEST:${NC} $1 ... "
|
|
}
|
|
|
|
pass() {
|
|
echo -e "${GREEN}PASS${NC}"
|
|
PASS=$((PASS + 1))
|
|
}
|
|
|
|
fail() {
|
|
echo -e "${RED}FAIL${NC} — $1"
|
|
FAIL=$((FAIL + 1))
|
|
FAILURES="${FAILURES}\n - $2: $1"
|
|
}
|
|
|
|
skip() {
|
|
echo -e "${YELLOW}SKIP${NC} — $1"
|
|
SKIP=$((SKIP + 1))
|
|
}
|
|
|
|
# Make an authenticated API call and return the body
|
|
api_get() {
|
|
local url="$1"
|
|
local jar="${2:-$COOKIE_JAR}"
|
|
curl -s -b "$jar" "${BASE_URL}${url}"
|
|
}
|
|
|
|
api_post() {
|
|
local url="$1"
|
|
local data="$2"
|
|
local jar="${3:-$COOKIE_JAR}"
|
|
curl -s -b "$jar" -X POST "${BASE_URL}${url}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data"
|
|
}
|
|
|
|
api_put() {
|
|
local url="$1"
|
|
local data="$2"
|
|
local jar="${3:-$COOKIE_JAR}"
|
|
curl -s -b "$jar" -X PUT "${BASE_URL}${url}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data"
|
|
}
|
|
|
|
api_delete() {
|
|
local url="$1"
|
|
local jar="${2:-$COOKIE_JAR}"
|
|
curl -s -b "$jar" -X DELETE "${BASE_URL}${url}"
|
|
}
|
|
|
|
# Get HTTP status code only
|
|
api_status() {
|
|
local method="${1}"
|
|
local url="${2}"
|
|
local jar="${3:-$COOKIE_JAR}"
|
|
curl -s -o /dev/null -w "%{http_code}" -b "$jar" -X "$method" "${BASE_URL}${url}"
|
|
}
|
|
|
|
# Extract JSON field (simple jq-like with grep)
|
|
json_field() {
|
|
echo "$1" | grep -o "\"$2\":[^,}]*" | head -1 | sed "s/\"$2\"://" | tr -d '"' | tr -d ' '
|
|
}
|
|
|
|
# Extract JSON string field
|
|
json_str() {
|
|
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//" | tr -d '"'
|
|
}
|
|
|
|
login() {
|
|
local email="$1"
|
|
local password="$2"
|
|
local jar="$3"
|
|
|
|
rm -f "$jar"
|
|
local csrf=$(curl -s -c "$jar" "${BASE_URL}/api/auth/csrf" | grep -o '"csrfToken":"[^"]*"' | cut -d'"' -f4)
|
|
curl -s -b "$jar" -c "$jar" -X POST "${BASE_URL}/api/auth/callback/credentials" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "email=${email}&password=${password}&csrfToken=${csrf}" \
|
|
-o /dev/null --max-redirs 5 -L 2>/dev/null || true
|
|
}
|
|
|
|
# =============================================================================
|
|
# PHASE 0: Connectivity Check
|
|
# =============================================================================
|
|
log_section "PHASE 0: Connectivity"
|
|
|
|
log_test "App is reachable at ${BASE_URL}"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/login" 2>/dev/null || echo "000")
|
|
if [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS — is Docker running?" "connectivity"
|
|
echo -e "\n${RED}Cannot reach the app. Run 'docker compose up -d' first.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 1: Authentication & Authorization
|
|
# =============================================================================
|
|
log_section "PHASE 1: Authentication & Authorization"
|
|
|
|
# --- Login as Admin ---
|
|
log_test "Admin login (admin@demo.com)"
|
|
login "admin@demo.com" "admin123" "$COOKIE_JAR"
|
|
SESSION=$(api_get "/api/auth/session")
|
|
USER_EMAIL=$(json_str "$SESSION" "email")
|
|
if [ "$USER_EMAIL" = "admin@demo.com" ]; then
|
|
pass
|
|
else
|
|
fail "Session email='$USER_EMAIL'" "admin-login"
|
|
fi
|
|
|
|
# --- Login as Super-admin ---
|
|
log_test "Super-admin login (superadmin@netforge.com)"
|
|
login "superadmin@netforge.com" "super123" "$COOKIE_JAR_SUPER"
|
|
SESSION=$(api_get "/api/auth/session" "$COOKIE_JAR_SUPER")
|
|
SA_EMAIL=$(json_str "$SESSION" "email")
|
|
if [ "$SA_EMAIL" = "superadmin@netforge.com" ]; then
|
|
pass
|
|
else
|
|
fail "Session email='$SA_EMAIL'" "superadmin-login"
|
|
fi
|
|
|
|
# --- Login as Tenant 2 Admin ---
|
|
log_test "Tenant 2 admin login (admin2@demo.com)"
|
|
login "admin2@demo.com" "admin123" "$COOKIE_JAR_TENANT2"
|
|
SESSION=$(api_get "/api/auth/session" "$COOKIE_JAR_TENANT2")
|
|
T2_EMAIL=$(json_str "$SESSION" "email")
|
|
if [ "$T2_EMAIL" = "admin2@demo.com" ]; then
|
|
pass
|
|
else
|
|
fail "Session email='$T2_EMAIL'" "tenant2-login"
|
|
fi
|
|
|
|
# --- Invalid login ---
|
|
log_test "Invalid login returns error"
|
|
rm -f /tmp/nf_bad_cookies.txt
|
|
CSRF=$(curl -s -c /tmp/nf_bad_cookies.txt "${BASE_URL}/api/auth/csrf" | grep -o '"csrfToken":"[^"]*"' | cut -d'"' -f4)
|
|
RESULT=$(curl -s -b /tmp/nf_bad_cookies.txt -c /tmp/nf_bad_cookies.txt -X POST "${BASE_URL}/api/auth/callback/credentials" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "email=admin@demo.com&password=wrongpassword&csrfToken=${CSRF}" \
|
|
-w "%{url_effective}" -L 2>/dev/null)
|
|
if echo "$RESULT" | grep -qi "error\|signin"; then
|
|
pass
|
|
else
|
|
fail "Expected error in redirect URL, got: $RESULT" "invalid-login"
|
|
fi
|
|
|
|
# --- Unauthenticated API access ---
|
|
log_test "Unauthenticated API returns 401 or 307 redirect"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/api/subscribers")
|
|
if [ "$STATUS" = "401" ] || [ "$STATUS" = "307" ]; then
|
|
pass
|
|
else
|
|
fail "Expected 401/307, got $STATUS" "unauth-api"
|
|
fi
|
|
|
|
# --- Super-admin tenant list ---
|
|
log_test "Super-admin can list all tenants"
|
|
TENANTS=$(api_get "/api/admin/tenants" "$COOKIE_JAR_SUPER")
|
|
if echo "$TENANTS" | grep -q "Demo ISP" && echo "$TENANTS" | grep -q "Test ISP 2"; then
|
|
pass
|
|
else
|
|
fail "Missing tenants in response" "superadmin-tenants"
|
|
fi
|
|
|
|
# --- Regular admin cannot access super-admin API ---
|
|
log_test "Admin cannot access /api/admin/tenants"
|
|
STATUS=$(api_status "GET" "/api/admin/tenants")
|
|
if [ "$STATUS" = "403" ] || [ "$STATUS" = "401" ]; then
|
|
pass
|
|
else
|
|
fail "Expected 401/403, got $STATUS" "admin-superadmin-deny"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 2: Service Plans & Subscribers
|
|
# =============================================================================
|
|
log_section "PHASE 2: Service Plans & Subscribers"
|
|
|
|
# --- Create service plan ---
|
|
log_test "Create POSTPAID service plan"
|
|
PLAN_RESP=$(api_post "/api/service-plans" "{
|
|
\"name\": \"UAT Basic 25Mbps ${RUN_ID}\",
|
|
\"speed\": \"25 Mbps\",
|
|
\"monthlyPrice\": 999,
|
|
\"billingType\": \"POSTPAID\",
|
|
\"description\": \"UAT test plan\"
|
|
}")
|
|
PLAN_ID=$(json_str "$PLAN_RESP" "id")
|
|
if [ -n "$PLAN_ID" ] && [ "$PLAN_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No plan ID returned: $PLAN_RESP" "create-plan"
|
|
PLAN_ID=""
|
|
fi
|
|
|
|
# --- Create PREPAID plan ---
|
|
log_test "Create PREPAID service plan"
|
|
PREPAID_RESP=$(api_post "/api/service-plans" "{
|
|
\"name\": \"UAT Prepaid 50Mbps ${RUN_ID}\",
|
|
\"speed\": \"50 Mbps\",
|
|
\"monthlyPrice\": 1499,
|
|
\"billingType\": \"PREPAID\",
|
|
\"description\": \"UAT prepaid plan\"
|
|
}")
|
|
PREPAID_PLAN_ID=$(json_str "$PREPAID_RESP" "id")
|
|
if [ -n "$PREPAID_PLAN_ID" ] && [ "$PREPAID_PLAN_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No plan ID: $PREPAID_RESP" "create-prepaid-plan"
|
|
PREPAID_PLAN_ID=""
|
|
fi
|
|
|
|
# --- List service plans ---
|
|
log_test "List service plans"
|
|
PLANS=$(api_get "/api/service-plans")
|
|
if echo "$PLANS" | grep -q "UAT Basic 25Mbps\|UAT"; then
|
|
pass
|
|
else
|
|
fail "Plan not found in list: $PLANS" "list-plans"
|
|
fi
|
|
|
|
# --- Create subscriber ---
|
|
log_test "Create subscriber"
|
|
if [ -n "$PLAN_ID" ]; then
|
|
SUB_RESP=$(api_post "/api/subscribers" "{
|
|
\"firstName\": \"Juan\",
|
|
\"lastName\": \"Dela Cruz\",
|
|
\"email\": \"juan@example.com\",
|
|
\"phone\": \"+639171234567\",
|
|
\"address\": \"123 Main St, Barangay 1\",
|
|
\"servicePlanId\": \"$PLAN_ID\"
|
|
}")
|
|
SUB_ID=$(json_str "$SUB_RESP" "id")
|
|
ACCT_NUM=$(json_str "$SUB_RESP" "accountNumber")
|
|
if [ -n "$SUB_ID" ] && [ "$SUB_ID" != "null" ]; then
|
|
pass
|
|
echo " → Subscriber ID: $SUB_ID, Account: $ACCT_NUM"
|
|
else
|
|
fail "No subscriber ID: $SUB_RESP" "create-subscriber"
|
|
SUB_ID=""
|
|
fi
|
|
else
|
|
skip "No plan ID from previous step"
|
|
SUB_ID=""
|
|
fi
|
|
|
|
# --- Create second subscriber ---
|
|
log_test "Create second subscriber"
|
|
if [ -n "$PREPAID_PLAN_ID" ]; then
|
|
SUB2_RESP=$(api_post "/api/subscribers" "{
|
|
\"firstName\": \"Maria\",
|
|
\"lastName\": \"Santos\",
|
|
\"email\": \"maria@example.com\",
|
|
\"phone\": \"+639181234567\",
|
|
\"address\": \"456 Oak Ave, Barangay 2\",
|
|
\"servicePlanId\": \"$PREPAID_PLAN_ID\"
|
|
}")
|
|
SUB2_ID=$(json_str "$SUB2_RESP" "id")
|
|
if [ -n "$SUB2_ID" ] && [ "$SUB2_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No subscriber ID: $SUB2_RESP" "create-subscriber-2"
|
|
SUB2_ID=""
|
|
fi
|
|
else
|
|
skip "No prepaid plan ID"
|
|
SUB2_ID=""
|
|
fi
|
|
|
|
# --- Search subscribers ---
|
|
log_test "Search subscribers by name"
|
|
SEARCH=$(api_get "/api/subscribers?search=Juan")
|
|
SEARCH_TOTAL=$(json_field "$SEARCH" "total")
|
|
if [ "$SEARCH_TOTAL" -ge 1 ] 2>/dev/null; then
|
|
pass
|
|
else
|
|
fail "Expected total=1, got $SEARCH_TOTAL" "search-subscribers"
|
|
fi
|
|
|
|
# --- Filter by status ---
|
|
log_test "Filter subscribers by ACTIVE status"
|
|
ACTIVE=$(api_get "/api/subscribers?status=ACTIVE")
|
|
ACTIVE_TOTAL=$(json_field "$ACTIVE" "total")
|
|
if [ "$ACTIVE_TOTAL" -ge 2 ] 2>/dev/null; then
|
|
pass
|
|
else
|
|
fail "Expected >=2 active, got $ACTIVE_TOTAL" "filter-active"
|
|
fi
|
|
|
|
# --- Get single subscriber ---
|
|
log_test "Get subscriber detail"
|
|
if [ -n "$SUB_ID" ]; then
|
|
DETAIL=$(api_get "/api/subscribers/$SUB_ID")
|
|
DETAIL_NAME=$(json_str "$DETAIL" "firstName")
|
|
if [ "$DETAIL_NAME" = "Juan" ]; then
|
|
pass
|
|
else
|
|
fail "Expected firstName=Juan, got $DETAIL_NAME" "get-subscriber"
|
|
fi
|
|
else
|
|
skip "No subscriber ID"
|
|
fi
|
|
|
|
# --- Subscriber balance ---
|
|
log_test "Get subscriber balance"
|
|
if [ -n "$SUB_ID" ]; then
|
|
BAL=$(api_get "/api/subscribers/$SUB_ID/balance")
|
|
if echo "$BAL" | grep -q "balance\|outstanding\|total"; then
|
|
pass
|
|
else
|
|
fail "Unexpected balance response: $BAL" "subscriber-balance"
|
|
fi
|
|
else
|
|
skip "No subscriber ID"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 2b: Billing & Payments
|
|
# =============================================================================
|
|
log_section "PHASE 2b: Billing & Payments"
|
|
|
|
# --- Generate invoices ---
|
|
log_test "Generate billing cycle"
|
|
BILLING_RESP=$(api_post "/api/billing/generate" '{}')
|
|
if echo "$BILLING_RESP" | grep -q "generated\|skipped\|results"; then
|
|
pass
|
|
GENERATED=$(json_field "$BILLING_RESP" "generated")
|
|
echo " → Generated: $GENERATED invoices"
|
|
else
|
|
fail "Unexpected billing response: $BILLING_RESP" "generate-billing"
|
|
fi
|
|
|
|
# --- List invoices ---
|
|
log_test "List invoices"
|
|
INVOICES=$(api_get "/api/invoices")
|
|
if echo "$INVOICES" | grep -q "invoices\|total\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected invoices response: $INVOICES" "list-invoices"
|
|
fi
|
|
|
|
# Extract first invoice ID if available
|
|
INVOICE_ID=$(echo "$INVOICES" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
|
|
# --- Record payment ---
|
|
log_test "Record payment against invoice"
|
|
if [ -n "$INVOICE_ID" ] && [ -n "$SUB_ID" ]; then
|
|
PAY_RESP=$(api_post "/api/payments" "{
|
|
\"subscriberId\": \"$SUB_ID\",
|
|
\"invoiceId\": \"$INVOICE_ID\",
|
|
\"amount\": 500,
|
|
\"method\": \"CASH\",
|
|
\"reference\": \"UAT-PAY-001\"
|
|
}")
|
|
PAY_ID=$(json_str "$PAY_RESP" "id")
|
|
if [ -n "$PAY_ID" ] && [ "$PAY_ID" != "null" ]; then
|
|
pass
|
|
echo " → Payment ID: $PAY_ID (partial ₱500)"
|
|
else
|
|
fail "No payment ID: $PAY_RESP" "record-payment"
|
|
PAY_ID=""
|
|
fi
|
|
else
|
|
skip "No invoice or subscriber ID"
|
|
PAY_ID=""
|
|
fi
|
|
|
|
# --- Record second payment (full remaining) ---
|
|
log_test "Record second payment (remaining balance)"
|
|
if [ -n "$INVOICE_ID" ] && [ -n "$SUB_ID" ]; then
|
|
PAY2_RESP=$(api_post "/api/payments" "{
|
|
\"subscriberId\": \"$SUB_ID\",
|
|
\"invoiceId\": \"$INVOICE_ID\",
|
|
\"amount\": 499,
|
|
\"method\": \"BANK_TRANSFER\",
|
|
\"reference\": \"UAT-PAY-002\"
|
|
}")
|
|
PAY2_ID=$(json_str "$PAY2_RESP" "id")
|
|
if [ -n "$PAY2_ID" ] && [ "$PAY2_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No payment ID: $PAY2_RESP" "record-payment-2"
|
|
fi
|
|
else
|
|
skip "No invoice or subscriber"
|
|
fi
|
|
|
|
# --- Payment history ---
|
|
log_test "Get subscriber payment history"
|
|
if [ -n "$SUB_ID" ]; then
|
|
PAYMENTS=$(api_get "/api/subscribers/$SUB_ID/payments")
|
|
if echo "$PAYMENTS" | grep -q "UAT-PAY\|payments\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $PAYMENTS" "payment-history"
|
|
fi
|
|
else
|
|
skip "No subscriber ID"
|
|
fi
|
|
|
|
# --- Outstanding report ---
|
|
log_test "Outstanding report"
|
|
OUTSTANDING=$(api_get "/api/reports/outstanding")
|
|
if echo "$OUTSTANDING" | grep -q "total\|results\|invoices\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $OUTSTANDING" "outstanding-report"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 2c: Accounting Foundation
|
|
# =============================================================================
|
|
log_section "PHASE 2c: Accounting Foundation"
|
|
|
|
# --- Chart of Accounts ---
|
|
log_test "List Chart of Accounts"
|
|
ACCOUNTS=$(api_get "/api/accounting/accounts")
|
|
ACCT_COUNT=$(echo "$ACCOUNTS" | grep -o '"code"' | wc -l)
|
|
if [ "$ACCT_COUNT" -gt 0 ] 2>/dev/null; then
|
|
pass
|
|
echo " → $ACCT_COUNT accounts found"
|
|
else
|
|
fail "COA is empty — seed.ts does not call seedChartOfAccounts() [APP BUG]" "chart-of-accounts"
|
|
echo " → This blocks: journal entries, expenses, financial reports with real data"
|
|
fi
|
|
|
|
# --- Journal entries ---
|
|
log_test "List journal entries"
|
|
JE_LIST=$(api_get "/api/accounting/journal-entries")
|
|
if echo "$JE_LIST" | grep -q "entries\|journalEntries\|total\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $JE_LIST" "list-journal-entries"
|
|
fi
|
|
|
|
# --- Manual journal entry ---
|
|
log_test "Create manual journal entry"
|
|
# Get account IDs for Cash on Hand (1010) and Petty Cash
|
|
ACCT_1010_ID=$(echo "$ACCOUNTS" | grep -o '"id":"[^"]*","code":"1010"' | head -1 | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
|
ACCT_1020_ID=$(echo "$ACCOUNTS" | grep -o '"id":"[^"]*","code":"1020"' | head -1 | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
|
if [ -n "$ACCT_1010_ID" ] && [ -n "$ACCT_1020_ID" ]; then
|
|
JE_RESP=$(api_post "/api/accounting/journal-entries" "{
|
|
\"description\": \"UAT: Transfer cash to bank\",
|
|
\"lines\": [
|
|
{ \"accountId\": \"$ACCT_1020_ID\", \"debit\": 1000, \"credit\": 0, \"description\": \"Deposit to bank\" },
|
|
{ \"accountId\": \"$ACCT_1010_ID\", \"debit\": 0, \"credit\": 1000, \"description\": \"From cash on hand\" }
|
|
]
|
|
}")
|
|
JE_ID=$(json_str "$JE_RESP" "id")
|
|
if [ -n "$JE_ID" ] && [ "$JE_ID" != "null" ]; then
|
|
pass
|
|
echo " → JE ID: $JE_ID"
|
|
else
|
|
fail "No JE ID: $JE_RESP" "create-journal-entry"
|
|
JE_ID=""
|
|
fi
|
|
else
|
|
skip "Could not find account IDs for 1010/1020"
|
|
JE_ID=""
|
|
fi
|
|
|
|
# --- Approve journal entry ---
|
|
log_test "Approve manual journal entry"
|
|
if [ -n "$JE_ID" ]; then
|
|
APPROVE_RESP=$(api_post "/api/accounting/journal-entries/$JE_ID/approve" '{}')
|
|
APPROVE_STATUS=$(json_str "$APPROVE_RESP" "status")
|
|
if [ "$APPROVE_STATUS" = "POSTED" ]; then
|
|
pass
|
|
else
|
|
fail "Expected POSTED, got $APPROVE_STATUS: $APPROVE_RESP" "approve-je"
|
|
fi
|
|
else
|
|
skip "No JE ID"
|
|
fi
|
|
|
|
# --- Account balance ---
|
|
log_test "Get account balance"
|
|
if [ -n "$ACCT_1010_ID" ]; then
|
|
BAL_RESP=$(api_get "/api/accounting/accounts/$ACCT_1010_ID/balance")
|
|
if echo "$BAL_RESP" | grep -q "balance\|debit\|credit"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $BAL_RESP" "account-balance"
|
|
fi
|
|
else
|
|
skip "No account ID"
|
|
fi
|
|
|
|
# --- Accounting periods ---
|
|
log_test "List accounting periods"
|
|
PERIODS=$(api_get "/api/accounting/periods")
|
|
if echo "$PERIODS" | grep -q "periods\|\[\]"; then
|
|
pass
|
|
else
|
|
# May return array directly
|
|
if echo "$PERIODS" | grep -q "\["; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $PERIODS" "accounting-periods"
|
|
fi
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 3a: Zones & Collectors
|
|
# =============================================================================
|
|
log_section "PHASE 3a: Zones & Collectors"
|
|
|
|
# --- Create zone ---
|
|
log_test "Create zone"
|
|
ZONE_RESP=$(api_post "/api/zones" "{
|
|
\"name\": \"UAT Zone North ${RUN_ID}\",
|
|
\"description\": \"Northern coverage area\"
|
|
}")
|
|
ZONE_ID=$(json_str "$ZONE_RESP" "id")
|
|
if [ -n "$ZONE_ID" ] && [ "$ZONE_ID" != "null" ]; then
|
|
pass
|
|
echo " → Zone ID: $ZONE_ID"
|
|
else
|
|
fail "No zone ID: $ZONE_RESP" "create-zone"
|
|
ZONE_ID=""
|
|
fi
|
|
|
|
# --- List zones ---
|
|
log_test "List zones"
|
|
ZONES=$(api_get "/api/zones")
|
|
if echo "$ZONES" | grep -q "UAT Zone North"; then
|
|
pass
|
|
else
|
|
fail "Zone not in list: $ZONES" "list-zones"
|
|
fi
|
|
|
|
# --- Assign subscriber to zone ---
|
|
log_test "Assign subscriber to zone"
|
|
if [ -n "$ZONE_ID" ] && [ -n "$SUB_ID" ]; then
|
|
ASSIGN_RESP=$(api_post "/api/zones/$ZONE_ID/subscribers" "{\"subscriberId\": \"$SUB_ID\"}")
|
|
if echo "$ASSIGN_RESP" | grep -q "$SUB_ID\|success\|zoneId"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $ASSIGN_RESP" "assign-sub-zone"
|
|
fi
|
|
else
|
|
skip "No zone or subscriber ID"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 3b: Ticketing System
|
|
# =============================================================================
|
|
log_section "PHASE 3b: Ticketing System"
|
|
|
|
# --- List ticket categories ---
|
|
log_test "List ticket categories"
|
|
CATS=$(api_get "/api/ticket-categories")
|
|
if echo "$CATS" | grep -q "\[\]\|categories\|Installation\|id"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $CATS" "list-ticket-categories"
|
|
fi
|
|
|
|
# --- Create ticket category ---
|
|
log_test "Create ticket category"
|
|
CAT_RESP=$(api_post "/api/ticket-categories" "{
|
|
\"name\": \"UAT Test Category ${RUN_ID}\",
|
|
\"description\": \"Category for UAT testing\"
|
|
}")
|
|
CAT_ID=$(json_str "$CAT_RESP" "id")
|
|
if [ -n "$CAT_ID" ] && [ "$CAT_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No category ID: $CAT_RESP" "create-ticket-category"
|
|
# Try to find any existing category
|
|
CAT_ID=$(echo "$CATS" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
fi
|
|
|
|
# --- Create ticket ---
|
|
log_test "Create support ticket"
|
|
if [ -n "$CAT_ID" ]; then
|
|
TICK_RESP=$(api_post "/api/tickets" "{
|
|
\"subject\": \"No internet connection\",
|
|
\"description\": \"Subscriber reports no connectivity since this morning\",
|
|
\"categoryId\": \"$CAT_ID\",
|
|
\"priority\": \"HIGH\"
|
|
}")
|
|
TICK_ID=$(json_str "$TICK_RESP" "id")
|
|
if [ -n "$TICK_ID" ] && [ "$TICK_ID" != "null" ]; then
|
|
pass
|
|
echo " → Ticket ID: $TICK_ID"
|
|
else
|
|
fail "No ticket ID: $TICK_RESP" "create-ticket"
|
|
TICK_ID=""
|
|
fi
|
|
else
|
|
skip "No category ID"
|
|
TICK_ID=""
|
|
fi
|
|
|
|
# --- List tickets ---
|
|
log_test "List tickets"
|
|
TICKETS=$(api_get "/api/tickets")
|
|
if echo "$TICKETS" | grep -q "No internet\|tickets\|total"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $TICKETS" "list-tickets"
|
|
fi
|
|
|
|
# --- Create second ticket ---
|
|
log_test "Create second ticket"
|
|
if [ -n "$CAT_ID" ]; then
|
|
TICK2_RESP=$(api_post "/api/tickets" "{
|
|
\"subject\": \"Slow connection speed\",
|
|
\"description\": \"Subscriber experiencing 2Mbps instead of 25Mbps\",
|
|
\"categoryId\": \"$CAT_ID\",
|
|
\"priority\": \"MEDIUM\"
|
|
}")
|
|
TICK2_ID=$(json_str "$TICK2_RESP" "id")
|
|
if [ -n "$TICK2_ID" ] && [ "$TICK2_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No ticket ID: $TICK2_RESP" "create-ticket-2"
|
|
TICK2_ID=""
|
|
fi
|
|
else
|
|
skip "No category ID"
|
|
TICK2_ID=""
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 3c: Technicians & Job Orders
|
|
# =============================================================================
|
|
log_section "PHASE 3c: Technicians & Job Orders"
|
|
|
|
# --- Create technician profile ---
|
|
log_test "Create technician profile"
|
|
# First we need a user with TECHNICIAN role — check if one exists or create via notes
|
|
TECH_RESP=$(api_post "/api/technicians" '{
|
|
"phone": "+639191234567",
|
|
"skills": ["Fiber Splicing", "Router Config"],
|
|
"compensationModel": "PER_JOB"
|
|
}')
|
|
TECH_ID=$(json_str "$TECH_RESP" "id")
|
|
if [ -n "$TECH_ID" ] && [ "$TECH_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
# May need a TECHNICIAN user — this is expected to fail without one
|
|
skip "No TECHNICIAN user in seed data: $TECH_RESP"
|
|
TECH_ID=""
|
|
fi
|
|
|
|
# --- Job type rates ---
|
|
log_test "List job type rates"
|
|
JTR=$(api_get "/api/job-type-rates")
|
|
if echo "$JTR" | grep -q "\[\]\|rates\|jobType"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $JTR" "list-job-type-rates"
|
|
fi
|
|
|
|
# --- Create job order from ticket ---
|
|
log_test "Create job order from ticket"
|
|
if [ -n "$TICK_ID" ]; then
|
|
# Job orders require assignedToId (a TECHNICIAN user). Seed data has no technician users.
|
|
skip "Requires TECHNICIAN user — seed data only has ADMIN users [APP GAP]"
|
|
JO_ID=""
|
|
else
|
|
skip "No ticket ID"
|
|
JO_ID=""
|
|
fi
|
|
|
|
# --- List job orders ---
|
|
log_test "List job orders"
|
|
JO_LIST=$(api_get "/api/job-orders")
|
|
if echo "$JO_LIST" | grep -q "jobOrders\|\[\]\|total\|id"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $JO_LIST" "list-job-orders"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 4a: Inventory & Assets
|
|
# =============================================================================
|
|
log_section "PHASE 4a: Inventory & Assets"
|
|
|
|
# --- Register inventory item ---
|
|
log_test "Register inventory item (SERIALIZED)"
|
|
INV_RESP=$(api_post "/api/inventory/items" '{
|
|
"name": "TP-Link ONU",
|
|
"itemType": "ONU",
|
|
"model": "XN020-G3v",
|
|
"serialNumber": "UAT-ONU-'"${RUN_ID}"'",
|
|
"trackingType": "SERIALIZED",
|
|
"purchaseCost": 1500
|
|
}')
|
|
INV_ID=$(json_str "$INV_RESP" "id")
|
|
if [ -n "$INV_ID" ] && [ "$INV_ID" != "null" ]; then
|
|
pass
|
|
echo " → Item ID: $INV_ID"
|
|
else
|
|
fail "No item ID: $INV_RESP" "register-item"
|
|
INV_ID=""
|
|
fi
|
|
|
|
# --- Register batch items ---
|
|
log_test "Register second inventory item (BATCH)"
|
|
INV2_RESP=$(api_post "/api/inventory/items" '{
|
|
"name": "Fiber Patch Cord",
|
|
"itemType": "CABLE",
|
|
"model": "SC-APC 3m",
|
|
"trackingType": "BATCH",
|
|
"purchaseCost": 150
|
|
}')
|
|
INV2_ID=$(json_str "$INV2_RESP" "id")
|
|
if [ -n "$INV2_ID" ] && [ "$INV2_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No item ID: $INV2_RESP" "register-item-2"
|
|
fi
|
|
|
|
# --- Record stock movement (RECEIVED) ---
|
|
log_test "Record RECEIVED stock movement"
|
|
if [ -n "$INV_ID" ]; then
|
|
MOV_RESP=$(api_post "/api/inventory/items/$INV_ID/movements" '{
|
|
"type": "RECEIVED",
|
|
"quantity": 10,
|
|
"notes": "Initial batch from supplier"
|
|
}')
|
|
if echo "$MOV_RESP" | grep -q "id\|movement"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $MOV_RESP" "stock-received"
|
|
fi
|
|
else
|
|
skip "No item ID"
|
|
fi
|
|
|
|
# --- Stock levels ---
|
|
log_test "Get stock levels"
|
|
STOCK=$(api_get "/api/inventory/stock-levels")
|
|
if echo "$STOCK" | grep -q "stock\|levels\|items\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $STOCK" "stock-levels"
|
|
fi
|
|
|
|
# --- Assign asset to subscriber ---
|
|
log_test "Assign asset to subscriber"
|
|
if [ -n "$INV_ID" ] && [ -n "$SUB_ID" ]; then
|
|
ASSIGN_RESP=$(api_post "/api/inventory/items/$INV_ID/assign" "{
|
|
\"assigneeType\": \"SUBSCRIBER\",
|
|
\"assigneeId\": \"$SUB_ID\",
|
|
\"condition\": \"NEW\",
|
|
\"notes\": \"ONU deployed at subscriber location\"
|
|
}")
|
|
if echo "$ASSIGN_RESP" | grep -q "id\|assigned\|success"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $ASSIGN_RESP" "assign-asset"
|
|
fi
|
|
else
|
|
skip "No item or subscriber ID"
|
|
fi
|
|
|
|
# --- Asset history ---
|
|
log_test "Get asset history"
|
|
if [ -n "$INV_ID" ]; then
|
|
HIST=$(api_get "/api/inventory/items/$INV_ID/history")
|
|
if echo "$HIST" | grep -q "history\|movements\|timeline\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $HIST" "asset-history"
|
|
fi
|
|
else
|
|
skip "No item ID"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 4b: Expenses
|
|
# =============================================================================
|
|
log_section "PHASE 4b: Expenses"
|
|
|
|
# --- Create expense category ---
|
|
log_test "Create expense category"
|
|
if [ "$ACCT_COUNT" -gt 0 ] 2>/dev/null; then
|
|
ECAT_RESP=$(api_post "/api/expenses/categories" '{
|
|
"name": "Bandwidth",
|
|
"accountCode": "5010"
|
|
}')
|
|
ECAT_ID=$(json_str "$ECAT_RESP" "id")
|
|
if [ -n "$ECAT_ID" ] && [ "$ECAT_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No category ID: $ECAT_RESP" "create-expense-cat"
|
|
ECAT_ID=""
|
|
fi
|
|
else
|
|
skip "No COA seeded — expense categories require valid account codes"
|
|
ECAT_ID=""
|
|
fi
|
|
|
|
# --- Create vendor ---
|
|
log_test "Create vendor"
|
|
VENDOR_RESP=$(api_post "/api/vendors" "{
|
|
\"name\": \"PLDT Enterprise ${RUN_ID}\",
|
|
\"contactPerson\": \"Sales Team\",
|
|
\"email\": \"sales@pldt.com\",
|
|
\"phone\": \"+6328888\"
|
|
}")
|
|
VENDOR_ID=$(json_str "$VENDOR_RESP" "id")
|
|
if [ -n "$VENDOR_ID" ] && [ "$VENDOR_ID" != "null" ]; then
|
|
pass
|
|
else
|
|
fail "No vendor ID: $VENDOR_RESP" "create-vendor"
|
|
VENDOR_ID=""
|
|
fi
|
|
|
|
# --- List vendors ---
|
|
log_test "List vendors"
|
|
VENDORS=$(api_get "/api/vendors")
|
|
if echo "$VENDORS" | grep -q "PLDT\|vendors\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $VENDORS" "list-vendors"
|
|
fi
|
|
|
|
# --- Create expense ---
|
|
log_test "Create expense"
|
|
if [ -n "$ECAT_ID" ] && [ -n "$VENDOR_ID" ]; then
|
|
EXP_RESP=$(api_post "/api/expenses" "{
|
|
\"amount\": 25000,
|
|
\"description\": \"Monthly bandwidth fee\",
|
|
\"categoryId\": \"$ECAT_ID\",
|
|
\"vendorId\": \"$VENDOR_ID\",
|
|
\"paymentMethod\": \"BANK_TRANSFER\",
|
|
\"expenseDate\": \"2026-03-06\"
|
|
}")
|
|
EXP_ID=$(json_str "$EXP_RESP" "id")
|
|
if [ -n "$EXP_ID" ] && [ "$EXP_ID" != "null" ]; then
|
|
pass
|
|
echo " → Expense ID: $EXP_ID"
|
|
else
|
|
fail "No expense ID: $EXP_RESP" "create-expense"
|
|
EXP_ID=""
|
|
fi
|
|
else
|
|
skip "No expense category or vendor ID"
|
|
EXP_ID=""
|
|
fi
|
|
|
|
# --- Expense reports ---
|
|
log_test "Expense report by category"
|
|
EXP_RPT=$(api_get "/api/reports/expenses?startDate=2026-01-01&endDate=2026-12-31")
|
|
if echo "$EXP_RPT" | grep -q "total\|expenses\|categories\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $EXP_RPT" "expense-report"
|
|
fi
|
|
|
|
log_test "Expense report by vendor"
|
|
EXP_RPT_V=$(api_get "/api/reports/expenses/by-vendor?startDate=2026-01-01&endDate=2026-12-31")
|
|
if echo "$EXP_RPT_V" | grep -q "total\|vendors\|expenses\|\[\]"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $EXP_RPT_V" "expense-by-vendor"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 4c: Financial Reports
|
|
# =============================================================================
|
|
log_section "PHASE 4c: Financial Reports"
|
|
|
|
# --- Trial Balance ---
|
|
log_test "Trial Balance"
|
|
TB=$(api_get "/api/reports/trial-balance")
|
|
if echo "$TB" | grep -q "isBalanced\|totalDebits\|accounts"; then
|
|
IS_BALANCED=$(json_field "$TB" "isBalanced")
|
|
if [ "$IS_BALANCED" = "true" ]; then
|
|
pass
|
|
echo " → Books are balanced"
|
|
else
|
|
fail "Trial balance NOT balanced: $TB" "trial-balance"
|
|
fi
|
|
else
|
|
fail "Unexpected: $TB" "trial-balance"
|
|
fi
|
|
|
|
# --- Income Statement ---
|
|
log_test "Income Statement"
|
|
IS_RPT=$(api_get "/api/reports/income-statement?startDate=2026-01-01&endDate=2026-12-31")
|
|
if echo "$IS_RPT" | grep -q "revenue\|expenses\|netIncome\|income"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $IS_RPT" "income-statement"
|
|
fi
|
|
|
|
# --- Balance Sheet ---
|
|
log_test "Balance Sheet"
|
|
BS=$(api_get "/api/reports/balance-sheet?asOfDate=2026-03-06")
|
|
if echo "$BS" | grep -q "assets\|liabilities\|equity\|balance"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $BS" "balance-sheet"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 5a: Dashboard
|
|
# =============================================================================
|
|
log_section "PHASE 5a: Dashboard"
|
|
|
|
log_test "Dashboard summary"
|
|
DASH=$(api_get "/api/dashboard")
|
|
if echo "$DASH" | grep -q "revenue" && echo "$DASH" | grep -q "subscribers" && echo "$DASH" | grep -q "cashFlow"; then
|
|
pass
|
|
REV_TODAY=$(json_str "$DASH" "revenueToday")
|
|
echo " → Revenue today: ₱$REV_TODAY"
|
|
else
|
|
fail "Incomplete dashboard: $DASH" "dashboard"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 5b: Client Portal
|
|
# =============================================================================
|
|
log_section "PHASE 5b: Client Portal (Subscriber Login)"
|
|
|
|
# Portal login requires a subscriber with a password set
|
|
# The seed data doesn't set subscriber passwords, so we test the API endpoints directly
|
|
|
|
log_test "Portal account endpoint (unauthenticated blocks access)"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/api/portal/account")
|
|
if [ "$STATUS" = "401" ] || [ "$STATUS" = "307" ]; then
|
|
pass
|
|
echo " → Returns $STATUS (middleware redirect to login)"
|
|
else
|
|
fail "Expected 401/307, got $STATUS" "portal-unauth"
|
|
fi
|
|
|
|
log_test "Portal invoices endpoint (unauthenticated blocks access)"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/api/portal/invoices")
|
|
if [ "$STATUS" = "401" ] || [ "$STATUS" = "307" ]; then
|
|
pass
|
|
else
|
|
fail "Expected 401/307, got $STATUS" "portal-invoices-unauth"
|
|
fi
|
|
|
|
log_test "Portal tickets endpoint (unauthenticated blocks access)"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/api/portal/tickets")
|
|
if [ "$STATUS" = "401" ] || [ "$STATUS" = "307" ]; then
|
|
pass
|
|
else
|
|
fail "Expected 401/307, got $STATUS" "portal-tickets-unauth"
|
|
fi
|
|
|
|
log_test "Portal payments coming-soon endpoint"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/api/portal/payments/coming-soon")
|
|
if [ "$STATUS" = "200" ] || [ "$STATUS" = "401" ] || [ "$STATUS" = "307" ]; then
|
|
pass
|
|
else
|
|
fail "Expected 200/401/307, got $STATUS" "portal-coming-soon"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 6: Tenant Isolation
|
|
# =============================================================================
|
|
log_section "PHASE 6: Tenant Isolation"
|
|
|
|
# --- Tenant 2 should NOT see Tenant 1 data ---
|
|
log_test "Tenant 2 cannot see Tenant 1 subscribers"
|
|
T2_SUBS=$(api_get "/api/subscribers" "$COOKIE_JAR_TENANT2")
|
|
T2_TOTAL=$(json_field "$T2_SUBS" "total")
|
|
if [ "$T2_TOTAL" = "0" ]; then
|
|
pass
|
|
else
|
|
fail "Tenant 2 sees $T2_TOTAL subscribers (should be 0)" "tenant-isolation-subs"
|
|
fi
|
|
|
|
log_test "Tenant 2 cannot see Tenant 1 service plans"
|
|
T2_PLANS=$(api_get "/api/service-plans" "$COOKIE_JAR_TENANT2")
|
|
if echo "$T2_PLANS" | grep -q "UAT.*Mbps"; then
|
|
fail "Tenant 2 can see Tenant 1 plans!" "tenant-isolation-plans"
|
|
else
|
|
pass
|
|
fi
|
|
|
|
log_test "Tenant 2 cannot see Tenant 1 tickets"
|
|
T2_TICKETS=$(api_get "/api/tickets" "$COOKIE_JAR_TENANT2")
|
|
if echo "$T2_TICKETS" | grep -q "No internet"; then
|
|
fail "Tenant 2 can see Tenant 1 tickets!" "tenant-isolation-tickets"
|
|
else
|
|
pass
|
|
fi
|
|
|
|
log_test "Tenant 2 cannot see Tenant 1 zones"
|
|
T2_ZONES=$(api_get "/api/zones" "$COOKIE_JAR_TENANT2")
|
|
if echo "$T2_ZONES" | grep -q "UAT Zone"; then
|
|
fail "Tenant 2 can see Tenant 1 zones!" "tenant-isolation-zones"
|
|
else
|
|
pass
|
|
fi
|
|
|
|
log_test "Tenant 2 cannot see Tenant 1 inventory"
|
|
T2_INV=$(api_get "/api/inventory/items" "$COOKIE_JAR_TENANT2")
|
|
if echo "$T2_INV" | grep -q "TP-Link"; then
|
|
fail "Tenant 2 can see Tenant 1 inventory!" "tenant-isolation-inv"
|
|
else
|
|
pass
|
|
fi
|
|
|
|
log_test "Tenant 2 dashboard shows zeros"
|
|
T2_DASH=$(api_get "/api/dashboard" "$COOKIE_JAR_TENANT2")
|
|
T2_SUBS_TOTAL=$(echo "$T2_DASH" | grep -o '"total":[0-9]*' | head -1 | cut -d: -f2)
|
|
if [ "$T2_SUBS_TOTAL" = "0" ]; then
|
|
pass
|
|
else
|
|
fail "Tenant 2 dashboard shows total=$T2_SUBS_TOTAL" "tenant-isolation-dash"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 7: UI Pages
|
|
# =============================================================================
|
|
log_section "PHASE 7: UI Pages"
|
|
|
|
log_test "Login page renders"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/login")
|
|
if [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS" "login-page"
|
|
fi
|
|
|
|
log_test "Signup page renders"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/signup")
|
|
if [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS" "signup-page"
|
|
fi
|
|
|
|
log_test "Dashboard page (authenticated)"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "${BASE_URL}/dashboard")
|
|
if [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS" "dashboard-page"
|
|
fi
|
|
|
|
log_test "Super-admin tenants page"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR_SUPER" "${BASE_URL}/admin/tenants")
|
|
if [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS" "admin-tenants-page"
|
|
fi
|
|
|
|
log_test "Dashboard page redirects when unauthenticated"
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/dashboard" --max-redirs 5 -L 2>/dev/null || true)
|
|
if [ "$STATUS" = "302" ] || [ "$STATUS" = "307" ] || [ "$STATUS" = "200" ]; then
|
|
pass
|
|
else
|
|
fail "HTTP $STATUS (expected redirect)" "dashboard-redirect"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 8: Data Integrity — Subscriber Status Lifecycle
|
|
# =============================================================================
|
|
log_section "PHASE 8: Data Integrity"
|
|
|
|
# --- Suspend subscriber ---
|
|
log_test "Suspend subscriber (PATCH)"
|
|
if [ -n "$SUB_ID" ]; then
|
|
SUSP_RESP=$(curl -s -b "$COOKIE_JAR" -X PATCH "${BASE_URL}/api/subscribers/$SUB_ID/status" \
|
|
-H "Content-Type: application/json" -d '{"status": "SUSPENDED"}')
|
|
NEW_STATUS=$(json_str "$SUSP_RESP" "status")
|
|
if [ "$NEW_STATUS" = "SUSPENDED" ]; then
|
|
pass
|
|
else
|
|
fail "Expected SUSPENDED, got $NEW_STATUS: $SUSP_RESP" "suspend-subscriber"
|
|
fi
|
|
else
|
|
skip "No subscriber ID"
|
|
fi
|
|
|
|
# --- Reactivate subscriber ---
|
|
log_test "Reactivate subscriber (PATCH)"
|
|
if [ -n "$SUB_ID" ]; then
|
|
REACT_RESP=$(curl -s -b "$COOKIE_JAR" -X PATCH "${BASE_URL}/api/subscribers/$SUB_ID/status" \
|
|
-H "Content-Type: application/json" -d '{"status": "ACTIVE"}')
|
|
NEW_STATUS=$(json_str "$REACT_RESP" "status")
|
|
if [ "$NEW_STATUS" = "ACTIVE" ]; then
|
|
pass
|
|
else
|
|
fail "Expected ACTIVE, got $NEW_STATUS: $REACT_RESP" "reactivate-subscriber"
|
|
fi
|
|
else
|
|
skip "No subscriber ID"
|
|
fi
|
|
|
|
# --- Void payment ---
|
|
log_test "Void payment"
|
|
if [ -n "$PAY_ID" ]; then
|
|
VOID_RESP=$(api_post "/api/payments/$PAY_ID/void" '{"reason": "UAT test void"}')
|
|
VOID_STATUS=$(json_str "$VOID_RESP" "status")
|
|
if [ "$VOID_STATUS" = "VOIDED" ]; then
|
|
pass
|
|
else
|
|
fail "Expected VOIDED, got $VOID_STATUS: $VOID_RESP" "void-payment"
|
|
fi
|
|
else
|
|
skip "No payment ID"
|
|
fi
|
|
|
|
# --- Trial Balance still balanced after all operations ---
|
|
log_test "Trial Balance still balanced after all operations"
|
|
TB_FINAL=$(api_get "/api/reports/trial-balance")
|
|
IS_BALANCED=$(json_field "$TB_FINAL" "isBalanced")
|
|
if [ "$IS_BALANCED" = "true" ]; then
|
|
pass
|
|
echo " → Books still balanced after all test operations"
|
|
else
|
|
fail "Trial balance NOT balanced after operations!" "final-trial-balance"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 9: Collections & Remittances
|
|
# =============================================================================
|
|
log_section "PHASE 9: Collections & Remittances"
|
|
|
|
# These require a COLLECTOR user which isn't in seed data
|
|
# Test the endpoints respond correctly with admin access
|
|
|
|
log_test "Collections endpoint responds"
|
|
COLL=$(api_get "/api/collections")
|
|
if echo "$COLL" | grep -q "collections\|\[\]\|error\|Forbidden"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $COLL" "collections-endpoint"
|
|
fi
|
|
|
|
log_test "Remittances endpoint responds"
|
|
REM=$(api_get "/api/remittances")
|
|
if echo "$REM" | grep -q "remittances\|\[\]\|error\|Forbidden"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $REM" "remittances-endpoint"
|
|
fi
|
|
|
|
log_test "Collection report endpoint responds"
|
|
COLL_RPT=$(api_get "/api/reports/collections")
|
|
if echo "$COLL_RPT" | grep -q "summary\|collectors\|\[\]\|error\|collections"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $COLL_RPT" "collections-report"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# PHASE 10: Compensation Reports
|
|
# =============================================================================
|
|
log_section "PHASE 10: Compensation Reports"
|
|
|
|
log_test "Compensation report endpoint"
|
|
COMP=$(api_get "/api/reports/compensation")
|
|
if echo "$COMP" | grep -q "technicians\|\[\]\|compensation\|error"; then
|
|
pass
|
|
else
|
|
fail "Unexpected: $COMP" "compensation-report"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# RESULTS
|
|
# =============================================================================
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${BOLD} UAT TEST RESULTS${NC}"
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
TOTAL=$((PASS + FAIL + SKIP))
|
|
echo -e " ${GREEN}PASSED:${NC} $PASS"
|
|
echo -e " ${RED}FAILED:${NC} $FAIL"
|
|
echo -e " ${YELLOW}SKIPPED:${NC} $SKIP"
|
|
echo -e " ${BOLD}TOTAL:${NC} $TOTAL"
|
|
echo ""
|
|
|
|
if [ $FAIL -gt 0 ]; then
|
|
echo -e " ${RED}${BOLD}Failures:${NC}"
|
|
echo -e "$FAILURES"
|
|
echo ""
|
|
fi
|
|
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo -e " ${GREEN}${BOLD}All tests passed!${NC}"
|
|
else
|
|
echo -e " ${YELLOW}Review failures above and test manually in the browser.${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Cleanup
|
|
rm -f /tmp/nf_bad_cookies.txt
|
|
|
|
exit $FAIL
|