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.
151 lines
5 KiB
Python
151 lines
5 KiB
Python
#!/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()
|