payfrit-qa/grubflip/api/test_endpoints.py
Luna (QA) e6153ac4b7 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.
2026-03-26 05:57:37 +00:00

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()