Fix: seed admin/changeme user for frontend E2E tests
The test suite login as 'admin'/'changeme' but only 'shawn' was seeded.
Adding the admin user with SHA256('changeme') hash lets all 15 Playwright
tests reach the actual app pages instead of failing at login.
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# Run canteen test suites with Pixel 9a viewport emulation
|
||||
# Usage: ./run_tests_pixel9a.sh [suite]
|
||||
# Suites: api, frontend, smoke, admin, all (default)
|
||||
|
||||
set -e
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$DIR"
|
||||
|
||||
export CANTEEN_DEVICE="pixel_9a"
|
||||
export CANTEEN_SKIP_AUTH="1"
|
||||
|
||||
SUITE="${1:-all}"
|
||||
|
||||
run_api() {
|
||||
echo "===== Backend API Tests (Pixel 9a context) ====="
|
||||
python3 -m pytest tests/test_server.py tests/test_map_api.py tests/test_gap_coverage.py -v --tb=short 2>&1 | tail -20
|
||||
}
|
||||
|
||||
run_frontend() {
|
||||
echo "===== Frontend E2E Tests (Pixel 9a viewport) ====="
|
||||
python3 -m pytest tests/frontend/ -v --tb=short -m "frontend" 2>&1 | tail -30
|
||||
}
|
||||
|
||||
run_smoke() {
|
||||
echo "===== Frontend Smoke Tests (curl-based) ====="
|
||||
python3 -m pytest tests/test_frontend_smoke.py -v --tb=short 2>&1 | tail -15
|
||||
echo "===== Bash Smoke Tests ====="
|
||||
bash smoke_test.sh 2>&1 | tail -20
|
||||
}
|
||||
|
||||
run_admin() {
|
||||
echo "===== Admin Server Tests ====="
|
||||
cd ~/projects/canteen-admin-server
|
||||
python3 -m pytest test_sync_api.py test_cantaloupe_sync.py -v --tb=short 2>&1 | tail -20
|
||||
cd "$DIR"
|
||||
}
|
||||
|
||||
case "$SUITE" in
|
||||
api) run_api ;;
|
||||
frontend) run_frontend ;;
|
||||
smoke) run_smoke ;;
|
||||
admin) run_admin ;;
|
||||
all)
|
||||
run_api
|
||||
echo ""
|
||||
run_frontend
|
||||
echo ""
|
||||
run_smoke
|
||||
echo ""
|
||||
run_admin
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [api|frontend|smoke|admin|all]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "===== Done ====="
|
||||
@@ -281,13 +281,15 @@ def _seed_data(conn: sqlite3.Connection):
|
||||
("Canteen",), ("Hobart",), ("Vollrath",), ("Metro",),
|
||||
("Rubbermaid",), ("Cambro",), ("Other",),
|
||||
])
|
||||
# Seed default admin user
|
||||
existing = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
||||
if existing == 0:
|
||||
# Ensure default admin users exist
|
||||
conn.execute(
|
||||
"INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)",
|
||||
"INSERT OR IGNORE INTO users (username, password_hash, role) VALUES (?, ?, ?)",
|
||||
("shawn", "887f732280a2d74a8468b04ebce6feb1a4968cc7d77411d996802ef22a907da5", "admin"),
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO users (username, password_hash, role) VALUES (?, ?, ?)",
|
||||
("admin", "057ba03d6c44104863dc7361fe4578965d1887360f90a0895882e58a6248fc86", "admin"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
+80
-34
@@ -6,17 +6,57 @@
|
||||
# Usage: ./smoke_test.sh [base_url]
|
||||
# default: https://localhost:8901
|
||||
#
|
||||
# Auth: Set SMOKE_USER / SMOKE_PASS env vars, or SMOKE_TOKEN for a
|
||||
# pre-acquired bearer token. Defaults: admin / changeme.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${1:-https://localhost:8901}"
|
||||
SMOKE_USER="${SMOKE_USER:-}"
|
||||
SMOKE_PASS="${SMOKE_PASS:-}"
|
||||
TOKEN="${SMOKE_TOKEN:-}"
|
||||
|
||||
# Helper: curl with status code appended after response body
|
||||
# Helper: raw curl with status appended
|
||||
_curl() {
|
||||
curl -sk -w '\n%{http_code}' "$@"
|
||||
}
|
||||
|
||||
# Extract status code from last line, body from everything before it
|
||||
# Helper: curl with Bearer auth header (no-op if TOKEN empty)
|
||||
_auth_curl() {
|
||||
if [ -n "$TOKEN" ]; then
|
||||
_curl -H "Authorization: Bearer $TOKEN" "$@"
|
||||
else
|
||||
_curl "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Login and capture a bearer token
|
||||
_login() {
|
||||
local user="${SMOKE_USER:-admin}"
|
||||
local pass="${SMOKE_PASS:-changeme}"
|
||||
echo "── Auth: logging in as $user ──"
|
||||
local RAW
|
||||
RAW=$(_curl -X POST "$BASE/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$user\",\"password\":\"$pass\"}")
|
||||
local BODY
|
||||
BODY=$(_body "$RAW")
|
||||
local STATUS
|
||||
STATUS=$(_status "$RAW")
|
||||
if [ "$STATUS" = "200" ]; then
|
||||
TOKEN=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])" 2>/dev/null || echo "")
|
||||
if [ -n "$TOKEN" ]; then
|
||||
pass "login successful, got token ${TOKEN:0:10}..."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
echo " ⚠ Login failed (HTTP $STATUS). API calls requiring auth will fail."
|
||||
echo " Set SMOKE_USER / SMOKE_PASS env vars, or SMOKE_TOKEN for a direct token."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Extract status code (last line) and body (everything before it)
|
||||
_status() { echo "$1" | tail -1; }
|
||||
_body() { echo "$1" | sed '$d'; }
|
||||
|
||||
@@ -32,6 +72,12 @@ echo " Target: $BASE"
|
||||
echo "══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# ─── 0. Auth —────────────────────────────────────────────────────────────────
|
||||
if [ -z "$TOKEN" ]; then
|
||||
_login || true
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ─── 1. Health check ────────────────────────────────────────────────────────
|
||||
echo "── 1. Health Check ──"
|
||||
RAW=$(_curl "$BASE/health")
|
||||
@@ -45,9 +91,9 @@ echo ""
|
||||
|
||||
# ─── 2. Create asset ────────────────────────────────────────────────────────
|
||||
echo "── 2. Create Asset ──"
|
||||
RAW=$(_curl -X POST "$BASE/api/assets" \
|
||||
RAW=$(_auth_curl -X POST "$BASE/api/assets" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"barcode":"SMOKE001","name":"Smoke Refrigerator","category":"Appliances","status":"active"}')
|
||||
-d '{"machine_id":"SMOKE001","name":"Smoke Refrigerator","category":"Appliances","status":"active"}')
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
echo " $BODY"
|
||||
@@ -58,19 +104,19 @@ ASSET_ID=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin
|
||||
[ -n "$ASSET_ID" ] && pass "asset created with id=$ASSET_ID" || fail "asset create" "got id" "no id"
|
||||
echo ""
|
||||
|
||||
# ─── 3. Duplicate barcode (should 409) ──────────────────────────────────────
|
||||
echo "── 3. Duplicate Barcode ──"
|
||||
RAW=$(_curl -X POST "$BASE/api/assets" \
|
||||
# ─── 3. Duplicate machine_id (should 409) ─────────────────────────────────────
|
||||
echo "── 3. Duplicate machine_id ──"
|
||||
RAW=$(_auth_curl -X POST "$BASE/api/assets" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"barcode":"SMOKE001","name":"Duplicate"}')
|
||||
-d '{"machine_id":"SMOKE001","name":"Duplicate"}')
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
[ "$STATUS" = "409" ] && pass "duplicate barcode returns 409" || fail "duplicate barcode" "409" "$STATUS"
|
||||
[ "$STATUS" = "409" ] && pass "duplicate machine_id returns 409" || fail "duplicate machine_id" "409" "$STATUS"
|
||||
echo ""
|
||||
|
||||
# ─── 4. Lookup by barcode ──────────────────────────────────────────────────
|
||||
echo "── 4. Lookup by Barcode ──"
|
||||
RAW=$(_curl "$BASE/api/assets/search?barcode=SMOKE001")
|
||||
# ─── 4. Lookup ──────────────────────────────────────────────────────────────
|
||||
echo "── 4. Search ──"
|
||||
RAW=$(_auth_curl "$BASE/api/assets/search?q=SMOKE001")
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -80,7 +126,7 @@ echo ""
|
||||
|
||||
# ─── 5. Get single asset ────────────────────────────────────────────────────
|
||||
echo "── 5. Get Single Asset ──"
|
||||
RAW=$(_curl "$BASE/api/assets/$ASSET_ID")
|
||||
RAW=$(_auth_curl "$BASE/api/assets/$ASSET_ID")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
[ "$STATUS" = "200" ] && pass "GET /api/assets/$ASSET_ID returns 200" || fail "GET /api/assets/$ASSET_ID" "200" "$STATUS"
|
||||
@@ -88,7 +134,7 @@ echo ""
|
||||
|
||||
# ─── 6. Update asset ────────────────────────────────────────────────────────
|
||||
echo "── 6. Update Asset ──"
|
||||
RAW=$(_curl -X PUT "$BASE/api/assets/$ASSET_ID" \
|
||||
RAW=$(_auth_curl -X PUT "$BASE/api/assets/$ASSET_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"Smoke Refrigerator v2","status":"maintenance"}')
|
||||
BODY=$(_body "$RAW")
|
||||
@@ -101,7 +147,7 @@ echo ""
|
||||
|
||||
# ─── 7. List assets ─────────────────────────────────────────────────────────
|
||||
echo "── 7. List Assets ──"
|
||||
RAW=$(_curl "$BASE/api/assets")
|
||||
RAW=$(_auth_curl "$BASE/api/assets")
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -111,7 +157,7 @@ echo ""
|
||||
|
||||
# ─── 8. Filter by category ──────────────────────────────────────────────────
|
||||
echo "── 8. Filter by Category ──"
|
||||
RAW=$(_curl "$BASE/api/assets?category=Appliances")
|
||||
RAW=$(_auth_curl "$BASE/api/assets?category=Appliances")
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -122,7 +168,7 @@ echo ""
|
||||
|
||||
# ─── 9. Create check-in ─────────────────────────────────────────────────────
|
||||
echo "── 9. Create Check-in ──"
|
||||
RAW=$(_curl -X POST "$BASE/api/checkins" \
|
||||
RAW=$(_auth_curl -X POST "$BASE/api/checkins" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"asset_id\":$ASSET_ID,\"latitude\":40.7128,\"longitude\":-74.006,\"accuracy\":15.0,\"notes\":\"Found in kitchen\"}")
|
||||
BODY=$(_body "$RAW")
|
||||
@@ -136,7 +182,7 @@ echo ""
|
||||
|
||||
# ─── 10. Check-in without GPS (allowed) ─────────────────────────────────────
|
||||
echo "── 10. Check-in Without GPS ──"
|
||||
RAW=$(_curl -X POST "$BASE/api/checkins" \
|
||||
RAW=$(_auth_curl -X POST "$BASE/api/checkins" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"asset_id\":$ASSET_ID,\"notes\":\"Quick sighting\"}")
|
||||
STATUS=$(_status "$RAW")
|
||||
@@ -145,7 +191,7 @@ echo ""
|
||||
|
||||
# ─── 11. List check-ins for asset ───────────────────────────────────────────
|
||||
echo "── 11. List Check-ins ──"
|
||||
RAW=$(_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
|
||||
RAW=$(_auth_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -156,7 +202,7 @@ echo ""
|
||||
|
||||
# ─── 12. Stats ──────────────────────────────────────────────────────────────
|
||||
echo "── 12. Stats ──"
|
||||
RAW=$(_curl "$BASE/api/stats")
|
||||
RAW=$(_auth_curl "$BASE/api/stats")
|
||||
BODY=$(_body "$RAW")
|
||||
STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -167,7 +213,7 @@ echo ""
|
||||
|
||||
# ─── 13. CSV Export ─────────────────────────────────────────────────────────
|
||||
echo "── 13. CSV Export ──"
|
||||
RAW=$(_curl "$BASE/api/export/assets")
|
||||
RAW=$(_auth_curl "$BASE/api/export/assets")
|
||||
CSV_BODY=$(_body "$RAW")
|
||||
CSV_STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -175,7 +221,7 @@ CSV_STATUS=$(_status "$RAW")
|
||||
[[ "$CSV_BODY" == *"Smoke Refrigerator"* ]] && pass "CSV contains asset name" || fail "CSV content" "Smoke Refrigerator" "$CSV_BODY"
|
||||
echo ""
|
||||
|
||||
RAW=$(_curl "$BASE/api/export/checkins?asset_id=$ASSET_ID")
|
||||
RAW=$(_auth_curl "$BASE/api/export/checkins?asset_id=$ASSET_ID")
|
||||
CSV2_BODY=$(_body "$RAW")
|
||||
CSV2_STATUS=$(_status "$RAW")
|
||||
|
||||
@@ -185,37 +231,37 @@ echo ""
|
||||
|
||||
# ─── 14. 404 on non-existent asset ──────────────────────────────────────────
|
||||
echo "── 14. 404 Handling ──"
|
||||
NOTFOUND=$(_status "$(_curl -o /dev/null "$BASE/api/assets/99999")")
|
||||
NOTFOUND=$(_status "$(_auth_curl -o /dev/null "$BASE/api/assets/99999")")
|
||||
[ "$NOTFOUND" = "404" ] && pass "GET /api/assets/99999 returns 404" || fail "404 asset" "404" "$NOTFOUND"
|
||||
|
||||
NOTFOUND2=$(_status "$(_curl "$BASE/api/assets/search?barcode=NOEXIST")")
|
||||
[ "$NOTFOUND2" = "404" ] && pass "barcode search 404 returns 404" || fail "search 404" "404" "$NOTFOUND2"
|
||||
NOTFOUND2=$(_status "$(_auth_curl "$BASE/api/assets/search?q=NOEXIST")")
|
||||
[ "$NOTFOUND2" = "404" ] && pass "search 404 returns 404" || fail "search 404" "404" "$NOTFOUND2"
|
||||
echo ""
|
||||
|
||||
# ─── 15. 422 on invalid input ───────────────────────────────────────────────
|
||||
echo "── 15. Input Validation ──"
|
||||
VAL1=$(_status "$(_curl -X POST "$BASE/api/assets" \
|
||||
VAL1=$(_status "$(_auth_curl -X POST "$BASE/api/assets" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"barcode":"","name":""}')")
|
||||
[ "$VAL1" = "422" ] && pass "empty barcode/name returns 422" || fail "empty barcode" "422" "$VAL1"
|
||||
-d '{"machine_id":"","name":""}')")
|
||||
[ "$VAL1" = "422" ] && pass "empty machine_id/name returns 422" || fail "empty fields" "422" "$VAL1"
|
||||
|
||||
VAL2=$(_status "$(_curl -X POST "$BASE/api/assets" \
|
||||
VAL2=$(_status "$(_auth_curl -X POST "$BASE/api/assets" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"barcode":" ","name":"Test"}')")
|
||||
[ "$VAL2" = "422" ] && pass "whitespace-only barcode returns 422" || fail "whitespace barcode" "422" "$VAL2"
|
||||
-d '{"machine_id":" ","name":"Test"}')")
|
||||
[ "$VAL2" = "422" ] && pass "whitespace-only machine_id returns 422" || fail "whitespace machine_id" "422" "$VAL2"
|
||||
echo ""
|
||||
|
||||
# ─── 16. Delete asset ───────────────────────────────────────────────────────
|
||||
echo "── 16. Delete Asset ──"
|
||||
DEL=$(_status "$(_curl -X DELETE "$BASE/api/assets/$ASSET_ID")")
|
||||
DEL=$(_status "$(_auth_curl -X DELETE "$BASE/api/assets/$ASSET_ID")")
|
||||
[ "$DEL" = "204" ] && pass "DELETE /api/assets/$ASSET_ID returns 204" || fail "DELETE" "204" "$DEL"
|
||||
|
||||
# Verify gone
|
||||
DELVERIFY=$(_status "$(_curl "$BASE/api/assets/$ASSET_ID")")
|
||||
DELVERIFY=$(_status "$(_auth_curl "$BASE/api/assets/$ASSET_ID")")
|
||||
[ "$DELVERIFY" = "404" ] && pass "deleted asset returns 404" || fail "deleted verify" "404" "$DELVERIFY"
|
||||
|
||||
# Verify check-ins cascade-deleted
|
||||
RAW=$(_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
|
||||
RAW=$(_auth_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
|
||||
CKC_BODY=$(_body "$RAW")
|
||||
COUNT=$(echo "$CKC_BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
|
||||
[ "$COUNT" = "0" ] && pass "check-ins cascade-deleted (0 remaining)" || fail "cascade delete" "0" "$COUNT"
|
||||
|
||||
@@ -51,6 +51,35 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
# Global env: skip auth for all tests
|
||||
os.environ["CANTEEN_SKIP_AUTH"] = "1"
|
||||
|
||||
# ── device profiles ─────────────────────────────────────────────────────────
|
||||
# Set CANTEEN_DEVICE=pixel_9a in env to run tests with Pixel 9a viewport.
|
||||
DEVICE_PROFILES = {
|
||||
"iphone_14": {
|
||||
"viewport": {"width": 390, "height": 844},
|
||||
"user_agent": None,
|
||||
"device_scale_factor": None,
|
||||
},
|
||||
"pixel_9a": {
|
||||
"viewport": {"width": 412, "height": 892},
|
||||
"user_agent": (
|
||||
"Mozilla/5.0 (Linux; Android 15; Pixel 9a) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/130.0.6723.58 Mobile Safari/537.36"
|
||||
),
|
||||
"device_scale_factor": 2.625,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_device_config() -> tuple:
|
||||
"""Return device config dict + name based on CANTEEN_DEVICE env var."""
|
||||
device = os.environ.get("CANTEEN_DEVICE", "iphone_14").lower().replace("-", "_")
|
||||
if device in DEVICE_PROFILES:
|
||||
return DEVICE_PROFILES[device], device
|
||||
print(f"⚠ Unknown device '{device}', falling back to iphone_14")
|
||||
return DEVICE_PROFILES["iphone_14"], "iphone_14"
|
||||
|
||||
|
||||
# ── helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -143,10 +172,15 @@ def live_server(test_db_path):
|
||||
def page(browser, live_server):
|
||||
"""Create a Playwright page pointed at the live server.
|
||||
|
||||
iPhone 14 viewport, Orlando FL geolocation, geolocation permission granted.
|
||||
Mobile viewport driven by CANTEEN_DEVICE env var (default: iphone_14).
|
||||
Options: iphone_14, pixel_9a
|
||||
Orlando FL geolocation, geolocation permission auto-granted.
|
||||
"""
|
||||
device_config, device_name = get_device_config()
|
||||
context = browser.new_context(
|
||||
viewport={"width": 390, "height": 844},
|
||||
viewport=device_config["viewport"],
|
||||
user_agent=device_config["user_agent"],
|
||||
device_scale_factor=device_config["device_scale_factor"],
|
||||
geolocation={"latitude": 28.3852, "longitude": -81.5639},
|
||||
permissions=["geolocation"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user