smoke-relay.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. #!/usr/bin/env python3
  2. """Run server-local smoke checks; never emit passwords, tokens or account data."""
  3. import argparse
  4. import json
  5. import sys
  6. from pathlib import Path
  7. from urllib.error import HTTPError
  8. from urllib.request import Request, urlopen
  9. parser = argparse.ArgumentParser()
  10. parser.add_argument('--base-url', default='http://127.0.0.1:8080')
  11. parser.add_argument('--env', type=Path, default=Path(__file__).with_name('.env'))
  12. args = parser.parse_args()
  13. settings = dict(line.split('=', 1) for line in args.env.read_text().splitlines()
  14. if line and not line.startswith('#') and '=' in line)
  15. results = []
  16. def request(path, *, method='GET', body=None, token=None):
  17. headers = {'Content-Type': 'application/json'}
  18. if token:
  19. headers['Authorization'] = 'Bearer ' + token
  20. req = Request(args.base_url.rstrip('/') + path,
  21. data=json.dumps(body).encode() if body is not None else None,
  22. headers=headers, method=method)
  23. try:
  24. response = urlopen(req, timeout=30)
  25. except HTTPError as error:
  26. response = error
  27. with response:
  28. raw = response.read()
  29. try:
  30. data = json.loads(raw)
  31. except (ValueError, UnicodeDecodeError):
  32. data = {}
  33. return response.status, data, raw
  34. def check(name, okay, **details):
  35. results.append({'name': name, 'passed': bool(okay), **details})
  36. status, data, _ = request('/health')
  37. check('health', status == 200 and data.get('status') == 'ok', status=status)
  38. status, data, _ = request('/api/v1/settings/public')
  39. check('public_settings', status == 200 and data.get('code') == 0, status=status)
  40. status, _, page = request('/home')
  41. check('frontend_shell', status == 200 and b'type="module"' in page, status=status)
  42. status, _, _ = request('/api/v1/keys')
  43. check('keys_require_auth', status == 401, status=status)
  44. status, _, _ = request('/v1/responses', method='POST', body={'model': 'test', 'input': 'hello'})
  45. check('gateway_requires_key', status == 401, status=status)
  46. status, data, _ = request('/api/v1/auth/login', method='POST', body={
  47. 'email': settings['ADMIN_EMAIL'], 'password': settings['ADMIN_PASSWORD']})
  48. token = (data.get('data') or {}).get('access_token')
  49. check('admin_login', status == 200 and bool(token), status=status)
  50. if token:
  51. for path in ['/api/v1/user/profile', '/api/v1/keys', '/api/v1/groups/available',
  52. '/api/v1/usage/dashboard/stats', '/api/v1/usage/dashboard/trend',
  53. '/api/v1/usage/dashboard/models', '/api/v1/usage?page=1&page_size=5',
  54. '/api/v1/user/platform-quotas']:
  55. status, data, _ = request(path, token=token)
  56. check(path, status == 200 and data.get('code') == 0, status=status)
  57. status, data, _ = request('/api/v1/admin/compliance', token=token)
  58. check('admin_confirmation_state_readable', status == 200, status=status)
  59. print(json.dumps({'scope': 'server HTTP/auth/database-backed user APIs; no upstream account call',
  60. 'checks': results, 'passed': all(item['passed'] for item in results)},
  61. ensure_ascii=False, indent=2))
  62. sys.exit(0 if all(item['passed'] for item in results) else 1)