Add QA test framework — API tests, infra health, test data seeding
Payfrit: - Tab API contract tests (bash + python) — all 13 endpoints - Param validation, response time, cross-env parity checks Grubflip: - API endpoint test stubs — menu, restaurant, order, auth - Ready to activate as Mike deploys endpoints Shared: - test_helpers.sh + test_helpers.py — HTTP helpers, pass/fail/skip, JSON output mode - Master test runner (scripts/run-all.sh) - Infrastructure health checker (disk, RAM, services, SSL certs) Test data: - Grubflip seed SQL (QA test restaurants, menus, orders) - Payfrit tab seeder script All Payfrit tab tests confirmed passing against dev + prod.
This commit is contained in:
parent
7d6d8c558e
commit
e6153ac4b7
16 changed files with 1031 additions and 2 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated reports
|
||||
reports/*.txt
|
||||
reports/*.json
|
||||
reports/*.md
|
||||
|
||||
# Temp files
|
||||
*.tmp
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
# Secrets (never commit)
|
||||
.env
|
||||
*.secret
|
||||
credentials.*
|
||||
47
README.md
47
README.md
|
|
@ -1,3 +1,46 @@
|
|||
# payfrit-qa
|
||||
# Payfrit QA Test Framework
|
||||
|
||||
QA test framework — API tests, infra health checks, test data seeding
|
||||
Automated test suites for Payfrit and Grubflip APIs, infrastructure health, and deployment validation.
|
||||
|
||||
**Maintainer:** Luna (@luna) — QA
|
||||
**Forgejo:** `payfrit/payfrit-qa`
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
payfrit/api/ — Payfrit Tab API contract tests
|
||||
grubflip/api/ — Grubflip API test suite
|
||||
shared/lib/ — Shared test utilities (bash + python)
|
||||
test-data/ — Seed data and fixtures
|
||||
scripts/ — CI runners, deployment checklists
|
||||
reports/ — Generated test reports (gitignored)
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all Payfrit API tests against dev
|
||||
./scripts/run-all.sh payfrit dev
|
||||
|
||||
# Run all Grubflip API tests against dev
|
||||
./scripts/run-all.sh grubflip dev
|
||||
|
||||
# Run infrastructure health check
|
||||
./scripts/infra-health.sh
|
||||
|
||||
# Seed test data
|
||||
./test-data/payfrit/seed-tabs.sh dev
|
||||
```
|
||||
|
||||
## Environments
|
||||
|
||||
| Name | Payfrit API | Grubflip API |
|
||||
|------|-------------|--------------|
|
||||
| dev | `https://dev.payfrit.com/api` | `https://dev.grubflip.com/api` |
|
||||
| prod | `https://biz.payfrit.com/api` | `https://api.grubflip.com` |
|
||||
|
||||
## Test Conventions
|
||||
|
||||
- Every test script exits 0 on all-pass, 1 on any failure.
|
||||
- JSON output mode available via `--json` flag for CI integration.
|
||||
- Reports written to `reports/` with timestamp filenames.
|
||||
|
|
|
|||
151
grubflip/api/test_endpoints.py
Normal file
151
grubflip/api/test_endpoints.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
test_endpoints.py — Grubflip API test suite
|
||||
|
||||
Tests Grubflip API endpoints as they come online.
|
||||
Structured as stubs that will be filled in as Mike deploys endpoints.
|
||||
|
||||
Current endpoint coverage (from Mike's task list):
|
||||
- Menu endpoints (CRUD)
|
||||
- Order endpoints
|
||||
- Restaurant endpoints
|
||||
- User endpoints
|
||||
- Auth endpoints
|
||||
|
||||
Usage:
|
||||
python3 grubflip/api/test_endpoints.py [dev|prod] [--json]
|
||||
|
||||
Author: Luna (@luna) — QA
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
|
||||
from shared.lib.test_helpers import TestRunner, get_base_url
|
||||
|
||||
|
||||
def main():
|
||||
env = "dev"
|
||||
json_mode = False
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "--json":
|
||||
json_mode = True
|
||||
elif arg in ("dev", "prod"):
|
||||
env = arg
|
||||
|
||||
base = get_base_url("grubflip", env)
|
||||
if not base:
|
||||
print(f"Unknown environment: {env}")
|
||||
sys.exit(1)
|
||||
|
||||
t = TestRunner(f"grubflip-api-{env}", json_mode=json_mode)
|
||||
|
||||
# ─── Connectivity check ──────────────────────────────────
|
||||
t.section("API CONNECTIVITY")
|
||||
resp = t.http_get(base)
|
||||
if resp["status"] == 0:
|
||||
t.fail("API base reachable", resp["error"])
|
||||
# If we can't even reach it, skip everything else
|
||||
exit_code = t.summary()
|
||||
sys.exit(exit_code)
|
||||
else:
|
||||
t.pass_(f"API base responds (HTTP {resp['status']}, {resp['time_ms']:.0f}ms)")
|
||||
|
||||
# ─── Menu endpoints ──────────────────────────────────────
|
||||
t.section("MENU ENDPOINTS")
|
||||
|
||||
menu_endpoints = [
|
||||
("GET", "/menus", "List menus"),
|
||||
("GET", "/menus/1", "Get menu by ID"),
|
||||
("GET", "/menu-items", "List menu items"),
|
||||
("GET", "/menu-items/1", "Get menu item by ID"),
|
||||
]
|
||||
|
||||
for method, path, label in menu_endpoints:
|
||||
url = f"{base}{path}"
|
||||
if method == "GET":
|
||||
resp = t.http_get(url)
|
||||
else:
|
||||
resp = t.http_post(url)
|
||||
|
||||
if resp["status"] == 0:
|
||||
t.skip(label, f"unreachable: {resp['error']}")
|
||||
elif resp["status"] == 404:
|
||||
t.skip(label, "404 — endpoint not deployed yet")
|
||||
elif resp["status"] in (200, 201, 400, 401, 422):
|
||||
t.pass_(f"{label} — HTTP {resp['status']} ({resp['time_ms']:.0f}ms)")
|
||||
else:
|
||||
t.fail(label, f"HTTP {resp['status']}")
|
||||
|
||||
# ─── Restaurant endpoints ────────────────────────────────
|
||||
t.section("RESTAURANT ENDPOINTS")
|
||||
|
||||
restaurant_endpoints = [
|
||||
("GET", "/restaurants", "List restaurants"),
|
||||
("GET", "/restaurants/1", "Get restaurant by ID"),
|
||||
]
|
||||
|
||||
for method, path, label in restaurant_endpoints:
|
||||
url = f"{base}{path}"
|
||||
resp = t.http_get(url)
|
||||
if resp["status"] == 0:
|
||||
t.skip(label, f"unreachable: {resp['error']}")
|
||||
elif resp["status"] == 404:
|
||||
t.skip(label, "404 — endpoint not deployed yet")
|
||||
elif resp["status"] in (200, 400, 401, 403, 422):
|
||||
t.pass_(f"{label} — HTTP {resp['status']} ({resp['time_ms']:.0f}ms)")
|
||||
else:
|
||||
t.fail(label, f"HTTP {resp['status']}")
|
||||
|
||||
# ─── Order endpoints ─────────────────────────────────────
|
||||
t.section("ORDER ENDPOINTS")
|
||||
|
||||
order_endpoints = [
|
||||
("GET", "/orders", "List orders"),
|
||||
("GET", "/orders/1", "Get order by ID"),
|
||||
("POST", "/orders", "Create order"),
|
||||
]
|
||||
|
||||
for method, path, label in order_endpoints:
|
||||
url = f"{base}{path}"
|
||||
if method == "POST":
|
||||
resp = t.http_post(url, {"test": True})
|
||||
else:
|
||||
resp = t.http_get(url)
|
||||
|
||||
if resp["status"] == 0:
|
||||
t.skip(label, f"unreachable: {resp['error']}")
|
||||
elif resp["status"] == 404:
|
||||
t.skip(label, "404 — endpoint not deployed yet")
|
||||
elif resp["status"] in (200, 201, 400, 401, 422):
|
||||
t.pass_(f"{label} — HTTP {resp['status']} ({resp['time_ms']:.0f}ms)")
|
||||
else:
|
||||
t.fail(label, f"HTTP {resp['status']}")
|
||||
|
||||
# ─── Auth endpoints ──────────────────────────────────────
|
||||
t.section("AUTH ENDPOINTS")
|
||||
|
||||
auth_endpoints = [
|
||||
("POST", "/auth/login", "Login"),
|
||||
("POST", "/auth/register", "Register"),
|
||||
]
|
||||
|
||||
for method, path, label in auth_endpoints:
|
||||
url = f"{base}{path}"
|
||||
resp = t.http_post(url, {})
|
||||
if resp["status"] == 0:
|
||||
t.skip(label, f"unreachable: {resp['error']}")
|
||||
elif resp["status"] == 404:
|
||||
t.skip(label, "404 — endpoint not deployed yet")
|
||||
elif resp["status"] in (200, 400, 401, 422):
|
||||
t.pass_(f"{label} — HTTP {resp['status']} ({resp['time_ms']:.0f}ms)")
|
||||
else:
|
||||
t.fail(label, f"HTTP {resp['status']}")
|
||||
|
||||
exit_code = t.summary()
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
64
grubflip/api/test_endpoints.sh
Executable file
64
grubflip/api/test_endpoints.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# test_endpoints.sh — Grubflip API smoke tests (bash)
|
||||
# Quick connectivity and endpoint existence checks.
|
||||
# Usage: ./grubflip/api/test_endpoints.sh [dev|prod] [--json]
|
||||
# Author: Luna (@luna) — QA
|
||||
# ============================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
source "$REPO_ROOT/shared/lib/test_helpers.sh"
|
||||
|
||||
ENV="${1:-dev}"
|
||||
case "$ENV" in
|
||||
dev) BASE="https://dev.grubflip.com/api" ;;
|
||||
prod) BASE="https://api.grubflip.com" ;;
|
||||
*) echo "Usage: $0 [dev|prod] [--json]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
$JSON_MODE || {
|
||||
echo "============================================"
|
||||
echo " GRUBFLIP API SMOKE TESTS ($ENV)"
|
||||
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
section "CONNECTIVITY"
|
||||
result=$(http_get "$BASE")
|
||||
IFS='|' read -r code body ms <<< "$result"
|
||||
if [ "$code" != "000" ] && [ -n "$code" ]; then
|
||||
pass "API base responds (HTTP $code, ${ms}ms)"
|
||||
else
|
||||
fail "API base unreachable" "HTTP $code"
|
||||
test_summary
|
||||
exit $?
|
||||
fi
|
||||
|
||||
section "ENDPOINT DISCOVERY"
|
||||
ENDPOINTS=(
|
||||
"GET|/menus|List menus"
|
||||
"GET|/menus/1|Get menu"
|
||||
"GET|/menu-items|List menu items"
|
||||
"GET|/restaurants|List restaurants"
|
||||
"GET|/restaurants/1|Get restaurant"
|
||||
"GET|/orders|List orders"
|
||||
"GET|/orders/1|Get order"
|
||||
)
|
||||
|
||||
for entry in "${ENDPOINTS[@]}"; do
|
||||
IFS='|' read -r method path label <<< "$entry"
|
||||
result=$(http_get "$BASE$path")
|
||||
IFS='|' read -r code body ms <<< "$result"
|
||||
|
||||
case "$code" in
|
||||
000) skip "$label ($path)" "unreachable" ;;
|
||||
404) skip "$label ($path)" "not deployed yet" ;;
|
||||
200|201|400|401|422|403)
|
||||
pass "$label ($path) — HTTP $code (${ms}ms)" ;;
|
||||
*)
|
||||
fail "$label ($path)" "HTTP $code" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
test_summary
|
||||
0
payfrit/__init__.py
Normal file
0
payfrit/__init__.py
Normal file
108
payfrit/api/test_tabs.py
Normal file
108
payfrit/api/test_tabs.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
test_tabs.py — Payfrit Tab API contract tests (Python version)
|
||||
|
||||
Tests all 13 tab endpoints for:
|
||||
- Endpoint existence (not 404)
|
||||
- Param validation (correct ERROR field when called with no params)
|
||||
- Response is valid JSON
|
||||
- Response time < 500ms
|
||||
|
||||
Usage:
|
||||
python3 payfrit/api/test_tabs.py [dev|prod] [--json]
|
||||
|
||||
Author: Luna (@luna) — QA
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add repo root to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
|
||||
from shared.lib.test_helpers import TestRunner, get_base_url
|
||||
|
||||
|
||||
def main():
|
||||
env = "dev"
|
||||
json_mode = False
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "--json":
|
||||
json_mode = True
|
||||
elif arg in ("dev", "prod"):
|
||||
env = arg
|
||||
|
||||
base = get_base_url("payfrit", env)
|
||||
if not base:
|
||||
print(f"Unknown environment: {env}")
|
||||
sys.exit(1)
|
||||
|
||||
t = TestRunner(f"payfrit-tabs-{env}", json_mode=json_mode)
|
||||
|
||||
# All 13 tab endpoints: (filename, expected_error_when_no_params)
|
||||
TAB_ENDPOINTS = [
|
||||
("open.php", "missing_UserID"),
|
||||
("get.php", "missing_TabID"),
|
||||
("getActive.php", "missing_UserID"),
|
||||
("getPresence.php", "missing_BusinessID"),
|
||||
("close.php", "missing_TabID"),
|
||||
("cancel.php", "missing_TabID"),
|
||||
("addOrder.php", "missing_TabID"),
|
||||
("addMember.php", "missing_TabID"),
|
||||
("removeMember.php", "missing_TabID"),
|
||||
("approveOrder.php", "missing_TabID"),
|
||||
("rejectOrder.php", "missing_TabID"),
|
||||
("pendingOrders.php", "missing_TabID"),
|
||||
("increaseAuth.php", "missing_TabID"),
|
||||
]
|
||||
|
||||
# --- Endpoint existence & param validation ---
|
||||
t.section(f"ENDPOINT EXISTENCE & PARAM VALIDATION ({len(TAB_ENDPOINTS)} endpoints)")
|
||||
|
||||
for endpoint, expected_error in TAB_ENDPOINTS:
|
||||
url = f"{base}/tabs/{endpoint}"
|
||||
resp = t.http_get(url)
|
||||
|
||||
if resp["status"] == 404 or resp["status"] == 0:
|
||||
t.fail(f"tabs/{endpoint}", f"HTTP {resp['status']} — endpoint missing or unreachable")
|
||||
continue
|
||||
|
||||
if resp["json"] is None:
|
||||
t.fail(f"tabs/{endpoint}", "response is not valid JSON")
|
||||
continue
|
||||
|
||||
actual_error = resp["json"].get("ERROR", "")
|
||||
if actual_error == expected_error:
|
||||
t.pass_(f"tabs/{endpoint} — param validation OK ({resp['time_ms']:.0f}ms)")
|
||||
else:
|
||||
t.fail(f"tabs/{endpoint}", f"expected ERROR='{expected_error}', got='{actual_error}'")
|
||||
|
||||
# --- Response time ---
|
||||
t.section("RESPONSE TIME (< 500ms threshold)")
|
||||
MAX_MS = 500
|
||||
|
||||
for endpoint, _ in TAB_ENDPOINTS:
|
||||
url = f"{base}/tabs/{endpoint}"
|
||||
resp = t.http_get(url)
|
||||
ms = resp["time_ms"]
|
||||
if ms < MAX_MS:
|
||||
t.pass_(f"tabs/{endpoint}: {ms:.0f}ms")
|
||||
else:
|
||||
t.fail(f"tabs/{endpoint}: {ms:.0f}ms", f"SLOW (limit {MAX_MS}ms)")
|
||||
|
||||
# --- Cross-environment parity ---
|
||||
if env == "dev":
|
||||
t.section("CROSS-ENVIRONMENT PARITY (dev vs prod)")
|
||||
prod_base = get_base_url("payfrit", "prod")
|
||||
for endpoint, _ in TAB_ENDPOINTS:
|
||||
resp = t.http_get(f"{prod_base}/tabs/{endpoint}")
|
||||
if resp["status"] not in (0, 404):
|
||||
t.pass_(f"tabs/{endpoint} exists on prod (HTTP {resp['status']})")
|
||||
else:
|
||||
t.fail(f"tabs/{endpoint} MISSING on prod (HTTP {resp['status']})")
|
||||
|
||||
exit_code = t.summary()
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
105
payfrit/api/test_tabs.sh
Executable file
105
payfrit/api/test_tabs.sh
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# test_tabs.sh — Payfrit Tab API Contract Tests
|
||||
# Tests all 13 tab endpoints for existence, param validation,
|
||||
# response format, and response time.
|
||||
# Usage: ./payfrit/api/test_tabs.sh [dev|prod] [--json]
|
||||
# Author: Luna (@luna) — QA
|
||||
# ============================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
source "$REPO_ROOT/shared/lib/test_helpers.sh"
|
||||
|
||||
ENV="${1:-dev}"
|
||||
case "$ENV" in
|
||||
dev) BASE="https://dev.payfrit.com/api" ;;
|
||||
prod) BASE="https://biz.payfrit.com/api" ;;
|
||||
*) echo "Usage: $0 [dev|prod] [--json]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
$JSON_MODE || {
|
||||
echo "============================================"
|
||||
echo " PAYFRIT TAB API CONTRACT TESTS ($ENV)"
|
||||
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
# All 13 tab endpoints with their expected error when called with no params
|
||||
TAB_ENDPOINTS=(
|
||||
"open.php|missing_UserID"
|
||||
"get.php|missing_TabID"
|
||||
"getActive.php|missing_UserID"
|
||||
"getPresence.php|missing_BusinessID"
|
||||
"close.php|missing_TabID"
|
||||
"cancel.php|missing_TabID"
|
||||
"addOrder.php|missing_TabID"
|
||||
"addMember.php|missing_TabID"
|
||||
"removeMember.php|missing_TabID"
|
||||
"approveOrder.php|missing_TabID"
|
||||
"rejectOrder.php|missing_TabID"
|
||||
"pendingOrders.php|missing_TabID"
|
||||
"increaseAuth.php|missing_TabID"
|
||||
)
|
||||
|
||||
# ─── Endpoint existence & param validation ────────────────────
|
||||
section "ENDPOINT EXISTENCE & PARAM VALIDATION (${#TAB_ENDPOINTS[@]} endpoints)"
|
||||
|
||||
for entry in "${TAB_ENDPOINTS[@]}"; do
|
||||
IFS='|' read -r endpoint expected_error <<< "$entry"
|
||||
url="$BASE/tabs/$endpoint"
|
||||
result=$(http_get "$url")
|
||||
IFS='|' read -r code body ms <<< "$result"
|
||||
|
||||
if [ "$code" = "404" ] || [ "$code" = "000" ]; then
|
||||
fail "tabs/$endpoint" "HTTP $code — endpoint missing"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_valid_json "$body"; then
|
||||
fail "tabs/$endpoint" "response is not valid JSON"
|
||||
continue
|
||||
fi
|
||||
|
||||
actual_error=$(json_field "$body" "ERROR")
|
||||
if [ "$actual_error" = "$expected_error" ]; then
|
||||
pass "tabs/$endpoint — correct param validation (${ms}ms)"
|
||||
else
|
||||
fail "tabs/$endpoint" "expected error='$expected_error', got='$actual_error'"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Response time checks ─────────────────────────────────────
|
||||
section "RESPONSE TIME (< 500ms threshold)"
|
||||
|
||||
MAX_MS=500
|
||||
for entry in "${TAB_ENDPOINTS[@]}"; do
|
||||
IFS='|' read -r endpoint _ <<< "$entry"
|
||||
url="$BASE/tabs/$endpoint"
|
||||
result=$(http_get "$url")
|
||||
IFS='|' read -r code body ms <<< "$result"
|
||||
|
||||
if [ "${ms:-0}" -lt "$MAX_MS" ] 2>/dev/null; then
|
||||
pass "tabs/$endpoint response: ${ms}ms"
|
||||
else
|
||||
fail "tabs/$endpoint response: ${ms}ms (SLOW, limit ${MAX_MS}ms)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Cross-environment parity ────────────────────────────────
|
||||
if [ "$ENV" = "dev" ]; then
|
||||
section "CROSS-ENVIRONMENT PARITY (dev vs prod)"
|
||||
PROD_BASE="https://biz.payfrit.com/api"
|
||||
for entry in "${TAB_ENDPOINTS[@]}"; do
|
||||
IFS='|' read -r endpoint _ <<< "$entry"
|
||||
prod_result=$(http_get "$PROD_BASE/tabs/$endpoint")
|
||||
IFS='|' read -r prod_code _ _ <<< "$prod_result"
|
||||
if [ "$prod_code" != "404" ] && [ "$prod_code" != "000" ]; then
|
||||
pass "tabs/$endpoint exists on prod (HTTP $prod_code)"
|
||||
else
|
||||
fail "tabs/$endpoint MISSING on prod (HTTP $prod_code)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
test_summary
|
||||
0
reports/.gitkeep
Normal file
0
reports/.gitkeep
Normal file
110
scripts/infra-health.sh
Executable file
110
scripts/infra-health.sh
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# infra-health.sh — Infrastructure health checks
|
||||
# Checks: disk, memory, apache, key services, SSL certs
|
||||
# Usage: ./scripts/infra-health.sh [--json]
|
||||
# Author: Luna (@luna) — QA
|
||||
# ============================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
source "$REPO_ROOT/shared/lib/test_helpers.sh"
|
||||
|
||||
$JSON_MODE || {
|
||||
echo "============================================"
|
||||
echo " INFRASTRUCTURE HEALTH CHECK"
|
||||
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo " Host: $(hostname)"
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
# ─── Disk ─────────────────────────────────────────────────────
|
||||
section "DISK USAGE"
|
||||
while read -r filesystem size used avail pct mount; do
|
||||
pct_num=${pct%%%}
|
||||
if [ "$pct_num" -lt 80 ]; then
|
||||
pass "$mount: ${pct} used ($avail available)"
|
||||
elif [ "$pct_num" -lt 90 ]; then
|
||||
fail "$mount: ${pct} used — WARNING" "$avail remaining"
|
||||
else
|
||||
fail "$mount: ${pct} used — CRITICAL" "$avail remaining"
|
||||
fi
|
||||
done < <(df -h --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs 2>/dev/null | tail -n +2)
|
||||
|
||||
# ─── Memory ───────────────────────────────────────────────────
|
||||
section "MEMORY"
|
||||
read -r total used free shared buff avail < <(free -m | awk '/^Mem:/ {print $2,$3,$4,$5,$6,$7}')
|
||||
pct_used=$((used * 100 / total))
|
||||
if [ "$pct_used" -lt 80 ]; then
|
||||
pass "RAM: ${used}M / ${total}M (${pct_used}%) — ${avail}M available"
|
||||
else
|
||||
fail "RAM: ${used}M / ${total}M (${pct_used}%)" "only ${avail}M available"
|
||||
fi
|
||||
|
||||
read -r stotal sused sfree < <(free -m | awk '/^Swap:/ {print $2,$3,$4}')
|
||||
if [ "${stotal:-0}" -gt 0 ]; then
|
||||
spct=$((sused * 100 / stotal))
|
||||
if [ "$spct" -lt 50 ]; then
|
||||
pass "Swap: ${sused}M / ${stotal}M (${spct}%)"
|
||||
else
|
||||
fail "Swap: ${sused}M / ${stotal}M (${spct}%)" "high swap usage"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── Key services ─────────────────────────────────────────────
|
||||
section "SERVICES"
|
||||
for svc in apache2 mysql mariadb; do
|
||||
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
||||
pass "$svc is running"
|
||||
elif systemctl list-unit-files "$svc.service" 2>/dev/null | grep -q "$svc"; then
|
||||
fail "$svc is NOT running"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── SSL cert checks ─────────────────────────────────────────
|
||||
section "SSL CERTIFICATES"
|
||||
DOMAINS=("payfrit.com" "dev.payfrit.com" "biz.payfrit.com" "grubflip.com" "dev.grubflip.com")
|
||||
for domain in "${DOMAINS[@]}"; do
|
||||
expiry=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||
cn=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -subject 2>/dev/null | grep -oP 'CN\s*=\s*\K.*')
|
||||
|
||||
if [ -z "$expiry" ]; then
|
||||
fail "$domain SSL" "could not retrieve certificate"
|
||||
continue
|
||||
fi
|
||||
|
||||
expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
|
||||
now_epoch=$(date +%s)
|
||||
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
|
||||
|
||||
if echo "$cn" | grep -qi "$domain"; then
|
||||
cn_ok="CN matches"
|
||||
else
|
||||
cn_ok="CN MISMATCH: $cn"
|
||||
fi
|
||||
|
||||
if [ "$days_left" -gt 30 ]; then
|
||||
pass "$domain — ${days_left}d until expiry, $cn_ok"
|
||||
elif [ "$days_left" -gt 0 ]; then
|
||||
fail "$domain — EXPIRING in ${days_left}d" "$cn_ok"
|
||||
else
|
||||
fail "$domain — EXPIRED" "$cn_ok"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Website reachability ────────────────────────────────────
|
||||
section "WEBSITE REACHABILITY"
|
||||
SITES=("https://payfrit.com" "https://dev.payfrit.com" "https://biz.payfrit.com" "https://grubflip.com" "https://dev.grubflip.com")
|
||||
for url in "${SITES[@]}"; do
|
||||
result=$(http_get "$url")
|
||||
IFS='|' read -r code body ms <<< "$result"
|
||||
if [ "$code" = "200" ]; then
|
||||
pass "$url — HTTP $code (${ms}ms)"
|
||||
elif [ "$code" = "000" ]; then
|
||||
fail "$url" "unreachable"
|
||||
else
|
||||
fail "$url" "HTTP $code (${ms}ms)"
|
||||
fi
|
||||
done
|
||||
|
||||
test_summary
|
||||
64
scripts/run-all.sh
Executable file
64
scripts/run-all.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# run-all.sh — Master test runner
|
||||
# Usage: ./scripts/run-all.sh [payfrit|grubflip|all] [dev|prod] [--json]
|
||||
# Author: Luna (@luna) — QA
|
||||
# ============================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPORTS_DIR="$REPO_ROOT/reports"
|
||||
|
||||
PROJECT="${1:-all}"
|
||||
ENV="${2:-dev}"
|
||||
JSON_FLAG=""
|
||||
for arg in "$@"; do [ "$arg" = "--json" ] && JSON_FLAG="--json"; done
|
||||
|
||||
TIMESTAMP=$(date -u '+%Y%m%d-%H%M%S')
|
||||
REPORT_FILE="$REPORTS_DIR/${PROJECT}-${ENV}-${TIMESTAMP}.txt"
|
||||
|
||||
mkdir -p "$REPORTS_DIR"
|
||||
|
||||
echo "╔══════════════════════════════════════════════╗"
|
||||
echo "║ PAYFRIT QA — TEST RUNNER ║"
|
||||
echo "║ Project: $PROJECT | Env: $ENV "
|
||||
echo "║ $(date -u '+%Y-%m-%d %H:%M:%S UTC') ║"
|
||||
echo "╚══════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
EXIT_CODE=0
|
||||
|
||||
run_suite() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
echo "─── Running: $name ───"
|
||||
if eval "$cmd"; then
|
||||
echo " → $name: ALL PASSED"
|
||||
else
|
||||
echo " → $name: FAILURES DETECTED"
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
if [ "$PROJECT" = "payfrit" ] || [ "$PROJECT" = "all" ]; then
|
||||
run_suite "Payfrit Tab API (bash)" "bash $REPO_ROOT/payfrit/api/test_tabs.sh $ENV $JSON_FLAG"
|
||||
run_suite "Payfrit Tab API (python)" "python3 $REPO_ROOT/payfrit/api/test_tabs.py $ENV $JSON_FLAG"
|
||||
fi
|
||||
|
||||
if [ "$PROJECT" = "grubflip" ] || [ "$PROJECT" = "all" ]; then
|
||||
run_suite "Grubflip API (bash)" "bash $REPO_ROOT/grubflip/api/test_endpoints.sh $ENV $JSON_FLAG"
|
||||
run_suite "Grubflip API (python)" "python3 $REPO_ROOT/grubflip/api/test_endpoints.py $ENV $JSON_FLAG"
|
||||
fi
|
||||
|
||||
echo "════════════════════════════════════════════════"
|
||||
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||
echo " ALL SUITES PASSED ✅"
|
||||
else
|
||||
echo " SOME SUITES HAD FAILURES ❌"
|
||||
fi
|
||||
echo "════════════════════════════════════════════════"
|
||||
|
||||
exit $EXIT_CODE
|
||||
0
shared/__init__.py
Normal file
0
shared/__init__.py
Normal file
0
shared/lib/__init__.py
Normal file
0
shared/lib/__init__.py
Normal file
152
shared/lib/test_helpers.py
Normal file
152
shared/lib/test_helpers.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""
|
||||
test_helpers.py — Shared Python test utilities for Payfrit QA
|
||||
Usage: from shared.lib.test_helpers import TestRunner
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import ssl
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# Skip SSL verification for dev/self-signed certs
|
||||
SSL_CTX = ssl.create_default_context()
|
||||
SSL_CTX.check_hostname = False
|
||||
SSL_CTX.verify_mode = ssl.CERT_NONE
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestResult:
|
||||
name: str
|
||||
status: str # pass, fail, skip
|
||||
detail: str = ""
|
||||
response_ms: float = 0
|
||||
|
||||
|
||||
class TestRunner:
|
||||
"""Lightweight test runner with console + JSON output."""
|
||||
|
||||
def __init__(self, suite_name: str, json_mode: bool = False):
|
||||
self.suite_name = suite_name
|
||||
self.json_mode = json_mode
|
||||
self.results: list[TestResult] = []
|
||||
|
||||
def pass_(self, name: str, detail: str = ""):
|
||||
r = TestResult(name, "pass", detail)
|
||||
self.results.append(r)
|
||||
if not self.json_mode:
|
||||
print(f" ✅ {name}{f' — {detail}' if detail else ''}")
|
||||
|
||||
def fail(self, name: str, detail: str = ""):
|
||||
r = TestResult(name, "fail", detail)
|
||||
self.results.append(r)
|
||||
if not self.json_mode:
|
||||
print(f" ❌ {name}{f' — {detail}' if detail else ''}")
|
||||
|
||||
def skip(self, name: str, reason: str = ""):
|
||||
r = TestResult(name, "skip", reason)
|
||||
self.results.append(r)
|
||||
if not self.json_mode:
|
||||
print(f" ⏭️ {name}{f' — {reason}' if reason else ''}")
|
||||
|
||||
def section(self, title: str):
|
||||
if not self.json_mode:
|
||||
print(f"\n━━━ {title} ━━━")
|
||||
|
||||
def http_get(self, url: str, timeout: int = 10) -> dict:
|
||||
"""Returns dict with keys: status, body, json, time_ms, error"""
|
||||
start = time.time()
|
||||
try:
|
||||
req = urllib.request.Request(url, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=timeout, context=SSL_CTX) as resp:
|
||||
body = resp.read().decode("utf-8", errors="replace")
|
||||
elapsed = (time.time() - start) * 1000
|
||||
result = {"status": resp.status, "body": body, "time_ms": elapsed, "error": None}
|
||||
try:
|
||||
result["json"] = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
result["json"] = None
|
||||
return result
|
||||
except urllib.error.HTTPError as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
body = e.read().decode("utf-8", errors="replace") if e.fp else ""
|
||||
result = {"status": e.code, "body": body, "time_ms": elapsed, "error": str(e)}
|
||||
try:
|
||||
result["json"] = json.loads(body)
|
||||
except (json.JSONDecodeError, Exception):
|
||||
result["json"] = None
|
||||
return result
|
||||
except Exception as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return {"status": 0, "body": "", "json": None, "time_ms": elapsed, "error": str(e)}
|
||||
|
||||
def http_post(self, url: str, data: dict = None, timeout: int = 10) -> dict:
|
||||
"""POST JSON, returns same dict as http_get."""
|
||||
payload = json.dumps(data or {}).encode("utf-8")
|
||||
start = time.time()
|
||||
try:
|
||||
req = urllib.request.Request(url, data=payload, method="POST",
|
||||
headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=timeout, context=SSL_CTX) as resp:
|
||||
body = resp.read().decode("utf-8", errors="replace")
|
||||
elapsed = (time.time() - start) * 1000
|
||||
result = {"status": resp.status, "body": body, "time_ms": elapsed, "error": None}
|
||||
try:
|
||||
result["json"] = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
result["json"] = None
|
||||
return result
|
||||
except urllib.error.HTTPError as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
body = e.read().decode("utf-8", errors="replace") if e.fp else ""
|
||||
result = {"status": e.code, "body": body, "time_ms": elapsed, "error": str(e)}
|
||||
try:
|
||||
result["json"] = json.loads(body)
|
||||
except (json.JSONDecodeError, Exception):
|
||||
result["json"] = None
|
||||
return result
|
||||
except Exception as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return {"status": 0, "body": "", "json": None, "time_ms": elapsed, "error": str(e)}
|
||||
|
||||
def summary(self) -> int:
|
||||
"""Print summary, return exit code (0=all pass, 1=any fail)."""
|
||||
passes = sum(1 for r in self.results if r.status == "pass")
|
||||
fails = sum(1 for r in self.results if r.status == "fail")
|
||||
skips = sum(1 for r in self.results if r.status == "skip")
|
||||
total = len(self.results)
|
||||
|
||||
if self.json_mode:
|
||||
out = {
|
||||
"suite": self.suite_name,
|
||||
"total": total, "pass": passes, "fail": fails, "skip": skips,
|
||||
"results": [{"name": r.name, "status": r.status, "detail": r.detail} for r in self.results]
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
else:
|
||||
print(f"\n{'━' * 42}")
|
||||
print(f" TOTAL: {total} | PASS: {passes} | FAIL: {fails} | SKIP: {skips}")
|
||||
print(f"{'━' * 42}")
|
||||
|
||||
return 0 if fails == 0 else 1
|
||||
|
||||
|
||||
# Environment config
|
||||
ENVS = {
|
||||
"payfrit": {
|
||||
"dev": "https://dev.payfrit.com/api",
|
||||
"prod": "https://biz.payfrit.com/api",
|
||||
},
|
||||
"grubflip": {
|
||||
"dev": "https://dev.grubflip.com/api",
|
||||
"prod": "https://api.grubflip.com",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_base_url(project: str, env: str) -> str:
|
||||
return ENVS.get(project, {}).get(env, "")
|
||||
101
shared/lib/test_helpers.sh
Normal file
101
shared/lib/test_helpers.sh
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# test_helpers.sh — Shared test utilities
|
||||
# Source this in any test script: source shared/lib/test_helpers.sh
|
||||
# ============================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
SKIP_COUNT=0
|
||||
TEST_RESULTS=()
|
||||
JSON_MODE=false
|
||||
|
||||
# Parse --json flag
|
||||
for arg in "$@"; do
|
||||
[ "$arg" = "--json" ] && JSON_MODE=true
|
||||
done
|
||||
|
||||
pass() {
|
||||
local msg="$1"
|
||||
((PASS_COUNT++))
|
||||
TEST_RESULTS+=("{\"status\":\"pass\",\"test\":\"$msg\"}")
|
||||
$JSON_MODE || echo -e " ${GREEN}✅${NC} $msg"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
local detail="${2:-}"
|
||||
((FAIL_COUNT++))
|
||||
TEST_RESULTS+=("{\"status\":\"fail\",\"test\":\"$msg\",\"detail\":\"$detail\"}")
|
||||
$JSON_MODE || echo -e " ${RED}❌${NC} $msg${detail:+ — $detail}"
|
||||
}
|
||||
|
||||
skip() {
|
||||
local msg="$1"
|
||||
local reason="${2:-}"
|
||||
((SKIP_COUNT++))
|
||||
TEST_RESULTS+=("{\"status\":\"skip\",\"test\":\"$msg\",\"reason\":\"$reason\"}")
|
||||
$JSON_MODE || echo -e " ${YELLOW}⏭️${NC} $msg${reason:+ — $reason}"
|
||||
}
|
||||
|
||||
section() {
|
||||
$JSON_MODE || echo -e "\n${CYAN}━━━ $1 ━━━${NC}"
|
||||
}
|
||||
|
||||
# HTTP helper: returns "status_code|body|time_ms"
|
||||
http_get() {
|
||||
local url="$1"
|
||||
local tmpfile=$(mktemp)
|
||||
local http_code time_total body
|
||||
http_code=$(curl -sk -o "$tmpfile" -w "%{http_code}" "$url" 2>/dev/null)
|
||||
time_total=$(curl -sk -o /dev/null -w "%{time_total}" "$url" 2>/dev/null)
|
||||
body=$(cat "$tmpfile")
|
||||
rm -f "$tmpfile"
|
||||
local ms=$(echo "$time_total * 1000" | bc 2>/dev/null | cut -d'.' -f1)
|
||||
echo "${http_code}|${body}|${ms:-0}"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url="$1"
|
||||
local data="${2:-{}}"
|
||||
local tmpfile=$(mktemp)
|
||||
local http_code
|
||||
http_code=$(curl -sk -X POST -o "$tmpfile" -w "%{http_code}" \
|
||||
-H "Content-Type: application/json" -d "$data" "$url" 2>/dev/null)
|
||||
local body=$(cat "$tmpfile")
|
||||
rm -f "$tmpfile"
|
||||
echo "${http_code}|${body}"
|
||||
}
|
||||
|
||||
# JSON field extractor (requires python3)
|
||||
json_field() {
|
||||
local json="$1"
|
||||
local field="$2"
|
||||
echo "$json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('$field',''))" 2>/dev/null
|
||||
}
|
||||
|
||||
is_valid_json() {
|
||||
echo "$1" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null
|
||||
}
|
||||
|
||||
# Print summary and exit with appropriate code
|
||||
test_summary() {
|
||||
local total=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))
|
||||
|
||||
if $JSON_MODE; then
|
||||
echo "{\"total\":$total,\"pass\":$PASS_COUNT,\"fail\":$FAIL_COUNT,\"skip\":$SKIP_COUNT,\"results\":[$(IFS=,; echo "${TEST_RESULTS[*]}")]}"
|
||||
else
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo -e " TOTAL: $total | ${GREEN}PASS: $PASS_COUNT${NC} | ${RED}FAIL: $FAIL_COUNT${NC} | ${YELLOW}SKIP: $SKIP_COUNT${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
fi
|
||||
|
||||
[ "$FAIL_COUNT" -eq 0 ] && return 0 || return 1
|
||||
}
|
||||
45
test-data/grubflip/seed.sql
Normal file
45
test-data/grubflip/seed.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- ============================================================
|
||||
-- Grubflip test data seed
|
||||
-- Run against grubflip_dev database
|
||||
-- Author: Luna (@luna) — QA
|
||||
-- ============================================================
|
||||
|
||||
-- Test restaurant
|
||||
INSERT IGNORE INTO restaurants (id, name, slug, address, phone, status, created_at)
|
||||
VALUES
|
||||
(9001, 'QA Test Restaurant', 'qa-test-restaurant', '123 Test St', '555-0100', 'active', NOW()),
|
||||
(9002, 'QA Inactive Restaurant', 'qa-inactive', '456 Test Ave', '555-0200', 'inactive', NOW());
|
||||
|
||||
-- Test menu
|
||||
INSERT IGNORE INTO menus (id, restaurant_id, name, status, created_at)
|
||||
VALUES
|
||||
(9001, 9001, 'QA Test Menu', 'active', NOW()),
|
||||
(9002, 9001, 'QA Draft Menu', 'draft', NOW());
|
||||
|
||||
-- Test menu items
|
||||
INSERT IGNORE INTO menu_items (id, menu_id, name, description, price, category, status, created_at)
|
||||
VALUES
|
||||
(9001, 9001, 'QA Burger', 'Test burger for QA', 9.99, 'Entrees', 'active', NOW()),
|
||||
(9002, 9001, 'QA Fries', 'Test fries for QA', 4.99, 'Sides', 'active', NOW()),
|
||||
(9003, 9001, 'QA Shake', 'Test milkshake for QA', 5.99, 'Drinks', 'active', NOW()),
|
||||
(9004, 9002, 'QA Draft Item', 'Should not appear in active queries', 7.99, 'Entrees', 'draft', NOW());
|
||||
|
||||
-- Test users
|
||||
INSERT IGNORE INTO users (id, email, name, phone, status, created_at)
|
||||
VALUES
|
||||
(9001, 'qa-user@test.payfrit.com', 'QA Test User', '555-0001', 'active', NOW()),
|
||||
(9002, 'qa-admin@test.payfrit.com', 'QA Admin User', '555-0002', 'active', NOW());
|
||||
|
||||
-- Test orders
|
||||
INSERT IGNORE INTO orders (id, user_id, restaurant_id, status, total, created_at)
|
||||
VALUES
|
||||
(9001, 9001, 9001, 'completed', 14.98, NOW()),
|
||||
(9002, 9001, 9001, 'pending', 9.99, NOW()),
|
||||
(9003, 9001, 9001, 'cancelled', 5.99, NOW());
|
||||
|
||||
-- Cleanup query (run manually when done testing):
|
||||
-- DELETE FROM orders WHERE id BETWEEN 9001 AND 9099;
|
||||
-- DELETE FROM menu_items WHERE id BETWEEN 9001 AND 9099;
|
||||
-- DELETE FROM menus WHERE id BETWEEN 9001 AND 9099;
|
||||
-- DELETE FROM restaurants WHERE id BETWEEN 9001 AND 9099;
|
||||
-- DELETE FROM users WHERE id BETWEEN 9001 AND 9099;
|
||||
72
test-data/payfrit/seed-tabs.sh
Executable file
72
test-data/payfrit/seed-tabs.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# seed-tabs.sh — Seed test data for tab API testing
|
||||
# Creates test users and businesses via the API so tab tests
|
||||
# can run with real data.
|
||||
# Usage: ./test-data/payfrit/seed-tabs.sh [dev|prod]
|
||||
# Author: Luna (@luna) — QA
|
||||
# ============================================================
|
||||
|
||||
ENV="${1:-dev}"
|
||||
case "$ENV" in
|
||||
dev) BASE="https://dev.payfrit.com/api" ;;
|
||||
prod) echo "ERROR: Will not seed prod. Use dev only."; exit 1 ;;
|
||||
*) echo "Usage: $0 [dev|prod]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "============================================"
|
||||
echo " TAB TEST DATA SEEDER ($ENV)"
|
||||
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Test user/business IDs — these should already exist in dev DB
|
||||
# If not, they need to be created via the user registration flow
|
||||
TEST_USER_ID="QA_USER_001"
|
||||
TEST_BUSINESS_ID="QA_BIZ_001"
|
||||
|
||||
echo "Test config:"
|
||||
echo " User ID: $TEST_USER_ID"
|
||||
echo " Business ID: $TEST_BUSINESS_ID"
|
||||
echo " API Base: $BASE"
|
||||
echo ""
|
||||
|
||||
# Try to open a test tab
|
||||
echo "Attempting to open a test tab..."
|
||||
RESPONSE=$(curl -sk "$BASE/tabs/open.php?UserID=$TEST_USER_ID&BusinessID=$TEST_BUSINESS_ID" 2>/dev/null)
|
||||
echo " Response: $RESPONSE"
|
||||
echo ""
|
||||
|
||||
# Check if we got a TabID back
|
||||
TAB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('TabID',''))" 2>/dev/null)
|
||||
if [ -n "$TAB_ID" ] && [ "$TAB_ID" != "None" ]; then
|
||||
echo "✅ Tab created: $TAB_ID"
|
||||
echo ""
|
||||
|
||||
# Try to get the tab
|
||||
echo "Fetching tab..."
|
||||
curl -sk "$BASE/tabs/get.php?TabID=$TAB_ID" 2>/dev/null | python3 -m json.tool 2>/dev/null
|
||||
echo ""
|
||||
|
||||
# Try to get active tabs for user
|
||||
echo "Fetching active tabs for user..."
|
||||
curl -sk "$BASE/tabs/getActive.php?UserID=$TEST_USER_ID" 2>/dev/null | python3 -m json.tool 2>/dev/null
|
||||
echo ""
|
||||
|
||||
# Clean up — cancel the test tab
|
||||
echo "Cancelling test tab..."
|
||||
curl -sk "$BASE/tabs/cancel.php?TabID=$TAB_ID" 2>/dev/null | python3 -m json.tool 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "✅ Seed complete — test tab created and cleaned up"
|
||||
else
|
||||
echo "⚠️ Could not create test tab. Response:"
|
||||
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
|
||||
echo ""
|
||||
echo "This may mean:"
|
||||
echo " - Test user/business don't exist in the DB yet"
|
||||
echo " - API requires auth that we don't have"
|
||||
echo " - Endpoint logic changed"
|
||||
echo ""
|
||||
echo "For now, tests will use paramless validation (which works)."
|
||||
fi
|
||||
Loading…
Add table
Reference in a new issue