Initial commit: Canteen Asset Geolocation Tool v2
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""API-level E2E tests for Canteen Asset Tracker."""
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
BASE = "https://canteen.ourpad.casa"
|
||||
results = {"passed": [], "failed": []}
|
||||
|
||||
def report(name, ok, detail=""):
|
||||
if ok:
|
||||
results["passed"].append(name)
|
||||
print(f" ✅ {name}")
|
||||
else:
|
||||
results["failed"].append((name, detail))
|
||||
print(f" ❌ {name}: {detail}")
|
||||
|
||||
def api(path, method="GET", token=None, json_data=None):
|
||||
headers = {}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
if json_data:
|
||||
headers["Content-Type"] = "application/json"
|
||||
url = f"{BASE}{path}"
|
||||
if method == "GET":
|
||||
r = requests.get(url, headers=headers, verify=False, timeout=15)
|
||||
elif method == "POST":
|
||||
r = requests.post(url, headers=headers, json=json_data, verify=False, timeout=15)
|
||||
else:
|
||||
raise ValueError(f"Unknown method: {method}")
|
||||
try:
|
||||
return r.status_code, r.json() if r.text else None
|
||||
except:
|
||||
return r.status_code, r.text
|
||||
|
||||
import urllib3
|
||||
urllib3.disable_warnings()
|
||||
|
||||
# ── 1. App reachable ──
|
||||
print("\n── 1. App Reachability ──")
|
||||
code, _ = api("/")
|
||||
report("App responds HTTP 200", code == 200, f"got {code}")
|
||||
|
||||
# ── 2. Login ──
|
||||
print("\n── 2. Login Flow ──")
|
||||
code, data = api("/api/auth/login", method="POST", json_data={"username": "admin", "password": "changeme"})
|
||||
login_ok = code == 200 and data and data.get("token")
|
||||
report("Login returns token", login_ok, f"code={code}, keys={list(data.keys()) if data else 'none'}")
|
||||
token = data.get("token") if data else None
|
||||
|
||||
# ── 3. Auth check ──
|
||||
print("\n── 3. Auth Verification ──")
|
||||
if token:
|
||||
code, me = api("/api/auth/me", token=token)
|
||||
me_ok = code == 200 and me and me.get("username") == "admin"
|
||||
report("Auth /me returns admin", me_ok, f"code={code}, data={str(me)[:200]}")
|
||||
else:
|
||||
report("Auth /me", False, "no token")
|
||||
|
||||
# ── 4. Assets CRUD ──
|
||||
print("\n── 4. Assets API ──")
|
||||
if token:
|
||||
code, assets = api("/api/assets", token=token)
|
||||
assets_ok = code == 200 and isinstance(assets, list)
|
||||
asset_count = len(assets) if isinstance(assets, list) else 0
|
||||
report(f"GET /api/assets returns list ({asset_count} items)", assets_ok, f"code={code}")
|
||||
|
||||
# Create asset (use valid category from seed data: Furniture, Appliances, etc.)
|
||||
test_mid = f"E2E-API-{int(time.time())}"
|
||||
code, created = api("/api/assets", method="POST", token=token, json_data={
|
||||
"machine_id": test_mid,
|
||||
"name": "E2E API Test Asset",
|
||||
"description": "Created via API E2E test",
|
||||
"category": "Equipment",
|
||||
"status": "active"
|
||||
})
|
||||
created_ok = code in (200, 201) and created and created.get("machine_id") == test_mid
|
||||
report("POST /api/assets creates asset", created_ok, f"code={code}, data={str(created)[:200]}")
|
||||
|
||||
# Verify in list
|
||||
if created_ok:
|
||||
code, assets2 = api("/api/assets", token=token)
|
||||
found = any(a.get("machine_id") == test_mid for a in assets2) if isinstance(assets2, list) else False
|
||||
report("New asset appears in list", found)
|
||||
|
||||
# ── 5. Public endpoints ──
|
||||
print("\n── 5. Public Endpoints ──")
|
||||
endpoints = [
|
||||
("/api/customers", "Customers"),
|
||||
("/api/locations", "Locations"),
|
||||
("/api/settings/categories", "Categories (settings)"),
|
||||
("/api/activity", "Activity feed"),
|
||||
("/api/stats", "Dashboard stats"),
|
||||
]
|
||||
for path, label in endpoints:
|
||||
code, data = api(path, token=token)
|
||||
ok = code == 200 and data is not None
|
||||
count_hint = f"({len(data)} items)" if isinstance(data, list) else f"({len(data)} keys)" if isinstance(data, dict) else ""
|
||||
report(f"GET {path} {count_hint}", ok, f"code={code}")
|
||||
|
||||
# ── 6. HTML structure verification ──
|
||||
print("\n── 6. Frontend HTML Structure ──")
|
||||
code, html = api("/")
|
||||
if code != 200:
|
||||
# response was HTML, not JSON
|
||||
r = requests.get(BASE, verify=False, timeout=15)
|
||||
html = r.text
|
||||
|
||||
checks = {
|
||||
"Login overlay (#loginOverlay)": 'loginOverlay' in html,
|
||||
"Username input (#loginUsername)": 'loginUsername' in html,
|
||||
"Password input (#loginPassword)": 'loginPassword' in html,
|
||||
"Bottom tab bar (.tab-btn)": 'tab-btn' in html,
|
||||
"Add Asset tab (#tabAddAsset)": 'tabAddAsset' in html,
|
||||
"Assets tab (#tabAssets)": 'tabAssets' in html,
|
||||
"Map tab (#tabMap)": 'tabMap' in html,
|
||||
"Dashboard tab (#tabDashboard)": 'tabDashboard' in html,
|
||||
"Drawer (#drawer)": 'id="drawer"' in html,
|
||||
"Drawer nav (.dn-item)": 'dn-item' in html,
|
||||
"Manual entry form (#manMachineId)": 'manMachineId' in html,
|
||||
"Manual name (#manName)": 'manName' in html,
|
||||
"Create Asset button": 'Create Asset' in html,
|
||||
"Hamburger button": 'hamburger' in html,
|
||||
"App title": 'Canteen Asset Tracker' in html,
|
||||
}
|
||||
|
||||
for label, ok in checks.items():
|
||||
report(label, ok)
|
||||
|
||||
# ── 7. Logout (no dedicated logout endpoint — token is stateless) ──
|
||||
print("\n── 7. Logout ──")
|
||||
# No /api/auth/logout endpoint exists. Tokens are likely stateless (no server-side invalidation).
|
||||
# The frontend clears the token client-side via doLogout().
|
||||
report("Logout: no server endpoint (client-side only)", True, "tokens are stateless — frontend clears locally")
|
||||
|
||||
# ── Summary ──
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RESULTS: {len(results['passed'])} passed, {len(results['failed'])} failed, 0 skipped")
|
||||
if results["failed"]:
|
||||
print("\nFAILURES:")
|
||||
for name, detail in results["failed"]:
|
||||
print(f" ❌ {name}: {detail}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("All tests passed! 🎉")
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user