#!/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)