zov-tech/tests/smoke_api.py
2026-05-17 17:59:41 +03:00

248 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Smoke-тесты API на боевом сервере.
Тестирует только публичные/анонимные эндпоинты (без initData).
Запуск: python tests/smoke_api.py [--url https://api.wasrusgen1.pro]
"""
import sys
import json
import urllib.request
import urllib.error
import argparse
import time
BASE_URL = "https://api.wasrusgen1.pro"
RESULTS = []
def check(name: str, ok: bool, detail: str = ""):
icon = "" if ok else ""
msg = f" {icon} {name}"
if detail:
msg += f" ({detail})"
RESULTS.append((ok, msg))
print(msg)
def get(path: str, timeout=10):
url = f"{BASE_URL}{path}"
try:
req = urllib.request.Request(url, headers={"User-Agent": "zov-smoke/1.0"})
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read().decode()
return r.status, body
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
except Exception as e:
return None, str(e)
def post(path: str, payload: dict, timeout=10):
url = f"{BASE_URL}{path}"
data = json.dumps(payload).encode()
try:
req = urllib.request.Request(
url, data=data,
headers={"Content-Type": "application/json", "User-Agent": "zov-smoke/1.0"},
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read().decode()
return r.status, body
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
except Exception as e:
return None, str(e)
# ─── Тесты ──────────────────────────────────────────────────────────────────
def test_healthz():
status, body = get("/healthz")
check("GET /healthz → 200", status == 200, f"status={status}")
def test_root():
status, body = get("/")
check("GET / → 200", status == 200, f"status={status}")
def test_me_no_auth():
"""Без initData должен вернуть ошибку аутентификации, но не 500."""
status, body = post("/api/me", {"initData": "", "role": "manager"})
try:
data = json.loads(body)
has_error_field = "error" in data
except Exception:
has_error_field = False
check(
"POST /api/me без initData → ошибка аутентификации (не 500)",
status in (200, 400, 403) and has_error_field,
f"status={status} error={data.get('error', '?') if has_error_field else body[:60]}",
)
def test_clients_no_auth():
status, body = post("/api/clients", {"initData": ""})
try:
data = json.loads(body)
ok = "error" in data and status != 500
except Exception:
ok = False
check("POST /api/clients без initData → auth-ошибка (не 500)", ok, f"status={status}")
def test_assembly_list_no_auth():
status, body = post("/api/assembly_list", {"initData": ""})
try:
data = json.loads(body)
ok = "error" in data and status != 500
except Exception:
ok = False
check("POST /api/assembly_list без initData → auth-ошибка (не 500)", ok, f"status={status}")
def test_measurement_request_no_auth():
status, body = post("/api/measurement_request", {
"initData": "",
"client_name": "Тест",
"client_phone": "79001234567",
})
try:
data = json.loads(body)
ok = "error" in data and status != 500
except Exception:
ok = False
check("POST /api/measurement_request без initData → auth-ошибка (не 500)", ok, f"status={status}")
def test_assembly_create_no_auth():
status, body = post("/api/assembly_create", {
"initData": "",
"client_name": "Тест",
"address": "Тест",
"scope_of_work": "Тест",
})
try:
data = json.loads(body)
ok = "error" in data and status != 500
except Exception:
ok = False
check("POST /api/assembly_create без initData → auth-ошибка (не 500)", ok, f"status={status}")
def test_proposal_list_no_auth():
status, body = post("/api/proposal_list", {"initData": ""})
try:
data = json.loads(body)
ok = "error" in data and status != 500
except Exception:
ok = False
check("POST /api/proposal_list без initData → auth-ошибка (не 500)", ok, f"status={status}")
def test_staff_list_no_auth():
status, body = post("/api/staff_list", {"initData": "", "role": "measurer"})
try:
data = json.loads(body)
# staff_list может вернуть пустой список без аутентификации — это ок
ok = status != 500
except Exception:
ok = False
check("POST /api/staff_list → не 500", ok, f"status={status}")
def test_photo_missing():
status, body = get("/api/photo/nonexistent_id/nonexistent.jpg")
check(
"GET /api/photo/несуществующий → 404 (не 500)",
status == 404,
f"status={status}",
)
def test_github_pages():
"""Проверяем что MiniApp доступен на GitHub Pages."""
import urllib.request
url = "https://wasrusgen.github.io/zov-tech/index.html"
try:
req = urllib.request.Request(url, headers={"User-Agent": "zov-smoke/1.0"})
with urllib.request.urlopen(req, timeout=15) as r:
body = r.read().decode()
has_app = 'id="app"' in body
check(
"GitHub Pages MiniApp доступен",
r.status == 200 and has_app,
f"status={r.status} has_app={has_app}",
)
except Exception as e:
check("GitHub Pages MiniApp доступен", False, str(e))
def test_miniapp_css_version():
"""CSS в index.html имеет версию (не закешируется по-старому)."""
import urllib.request
import re
url = "https://wasrusgen.github.io/zov-tech/index.html"
try:
req = urllib.request.Request(url, headers={"User-Agent": "zov-smoke/1.0"})
with urllib.request.urlopen(req, timeout=15) as r:
body = r.read().decode()
has_version = bool(re.search(r'styles\.css\?v=\d{8}[a-z]', body))
check(
"index.html: styles.css имеет версию вида ?v=YYYYMMDDx",
has_version,
f"found={has_version}",
)
except Exception as e:
check("index.html: проверка версии", False, str(e))
# ─── Main ───────────────────────────────────────────────────────────────────
def main():
global BASE_URL
parser = argparse.ArgumentParser()
parser.add_argument("--url", default=BASE_URL, help="Base URL бэкенда")
args = parser.parse_args()
BASE_URL = args.url.rstrip("/")
print(f"🔥 Smoke-тесты → {BASE_URL}\n")
t0 = time.time()
test_healthz()
test_root()
test_me_no_auth()
test_clients_no_auth()
test_assembly_list_no_auth()
test_measurement_request_no_auth()
test_assembly_create_no_auth()
test_proposal_list_no_auth()
test_staff_list_no_auth()
test_photo_missing()
test_github_pages()
test_miniapp_css_version()
elapsed = time.time() - t0
passed = sum(1 for ok, _ in RESULTS if ok)
failed = len(RESULTS) - passed
print(f"\n{''*50}")
print(f" Итого: {passed} пройдено / {failed} упало ({elapsed:.1f}s)")
if failed:
print(f"\n🚫 Замечания к устранению:")
for ok, msg in RESULTS:
if not ok:
print(msg)
print()
sys.exit(1)
else:
print("\nВсе тесты прошли.\n")
sys.exit(0)
if __name__ == "__main__":
main()