114 lines
5.2 KiB
Python
114 lines
5.2 KiB
Python
"""Read-only HTTP checks for the isolated localhost:18080 deployment rehearsal."""
|
|||
|
|
import hashlib
|
||
|
|
import http.cookiejar
|
||
|
|
from html.parser import HTMLParser
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
import urllib.error
|
||
|
|
import urllib.parse
|
||
|
|
import urllib.request
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
BASE = "http://localhost:18080"
|
||
|
|
|
||
|
|
|
||
|
|
class Links(HTMLParser):
|
||
|
|
def __init__(self):
|
||
|
|
super().__init__()
|
||
|
|
self.urls = []
|
||
|
|
|
||
|
|
def handle_starttag(self, tag, attrs):
|
||
|
|
values = dict(attrs)
|
||
|
|
if tag in ("script", "img") and values.get("src"):
|
||
|
|
self.urls.append(values["src"])
|
||
|
|
if tag == "link" and values.get("href"):
|
||
|
|
self.urls.append(values["href"])
|
||
|
|
|
||
|
|
|
||
|
|
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||
|
|
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
credentials = dict(line.split(": ", 1) for line in
|
||
|
|
(ROOT / "artifacts/preview-access.txt").read_text().splitlines())
|
||
|
|
opener = urllib.request.build_opener(NoRedirect)
|
||
|
|
cookies = http.cookiejar.CookieJar()
|
||
|
|
signed_in = urllib.request.build_opener(NoRedirect, urllib.request.HTTPCookieProcessor(cookies))
|
||
|
|
|
||
|
|
def request(path, status=200, authenticated=False, data=None, headers=None):
|
||
|
|
headers = dict(headers or {})
|
||
|
|
if data is not None:
|
||
|
|
headers["Content-Type"] = "application/json"
|
||
|
|
req = urllib.request.Request(BASE + path, headers=headers,
|
||
|
|
data=json.dumps(data).encode() if data is not None else None)
|
||
|
|
try:
|
||
|
|
response = (signed_in if authenticated else opener).open(req, timeout=15)
|
||
|
|
except urllib.error.HTTPError as error:
|
||
|
|
response = error
|
||
|
|
with response:
|
||
|
|
body = response.read()
|
||
|
|
assert response.status == status, (path, response.status, status)
|
||
|
|
assert "WWW-Authenticate" not in response.headers, path
|
||
|
|
return body, response.headers
|
||
|
|
|
||
|
|
portal, _ = request("/")
|
||
|
|
assert b'href="/blackjack/"' in portal and b'href="/wolin/"' in portal
|
||
|
|
game, _ = request("/blackjack/")
|
||
|
|
assert hashlib.sha256(game).digest() == hashlib.sha256(
|
||
|
|
(ROOT / "deploy/site/blackjack/index.html").read_bytes()).digest()
|
||
|
|
for path in ["/blackjack", "/wolin"]:
|
||
|
|
_, headers = request(path, 308)
|
||
|
|
assert headers["Location"].endswith(path + "/")
|
||
|
|
_, headers = request("/wolin/", 303, headers={"Accept": "text/html"})
|
||
|
|
assert headers["Location"] == "/wolin/login"
|
||
|
|
login_page, _ = request("/wolin/login")
|
||
|
|
assert b'id="login-form"' in login_page
|
||
|
|
for path in ["/wolin/docs", "/wolin/openapi.json", "/wolin/static/app.js",
|
||
|
|
"/wolin/api/studentsManagement/workspace/overview",
|
||
|
|
"/wolin/api/studentsManagement/ai/status"]:
|
||
|
|
request(path, 401)
|
||
|
|
request("/wolin/api/studentsManagement/workspace/query", 401, data={"module": "students"})
|
||
|
|
for path in ["/api/studentsManagement/workspace/overview", "/docs", "/openapi.json",
|
||
|
|
"/static/app.js", "/.env", "/.env.cloud", "/.git/config"]:
|
||
|
|
request(path, 404)
|
||
|
|
|
||
|
|
request("/wolin/auth/login", 401, data={"username": credentials["Username"], "password": "incorrect-test-password"})
|
||
|
|
request("/wolin/auth/login", authenticated=True,
|
||
|
|
data={"username": credentials["Username"], "password": credentials["Password"]},
|
||
|
|
headers={"Origin": BASE})
|
||
|
|
assert cookies, "Login did not issue a session cookie"
|
||
|
|
assert all(cookie.path == "/wolin/" and cookie.has_nonstandard_attr("HttpOnly") for cookie in cookies)
|
||
|
|
page, headers = request("/wolin/", authenticated=True)
|
||
|
|
assert headers["Cache-Control"] == "no-store"
|
||
|
|
parser = Links()
|
||
|
|
parser.feed(page.decode())
|
||
|
|
for url in parser.urls:
|
||
|
|
if urllib.parse.urlsplit(url).scheme:
|
||
|
|
continue
|
||
|
|
path = urllib.parse.urlsplit(urllib.parse.urljoin(BASE + "/wolin/", url)).path
|
||
|
|
assert path.startswith("/wolin/"), path
|
||
|
|
request(path, authenticated=True)
|
||
|
|
request("/wolin/docs", authenticated=True)
|
||
|
|
schema, _ = request("/wolin/openapi.json", authenticated=True)
|
||
|
|
assert any(server["url"] == "/wolin" for server in json.loads(schema)["servers"])
|
||
|
|
body, _ = request("/wolin/api/studentsManagement/workspace/overview", authenticated=True)
|
||
|
|
overview = json.loads(body)
|
||
|
|
for module in ["students", "classes", "teachers", "advisors", "scores", "employment"]:
|
||
|
|
request("/wolin/api/studentsManagement/workspace/query", authenticated=True,
|
||
|
|
data={"module": module, "page": 1, "page_size": 1})
|
||
|
|
request("/wolin/auth/logout", authenticated=True, data={})
|
||
|
|
assert not list(cookies), "Logout did not clear the session"
|
||
|
|
request("/wolin/api/studentsManagement/workspace/overview", 401, authenticated=True)
|
||
|
|
report = {"portal": "ok", "game_bytes_match_online_build": True,
|
||
|
|
"authentication": "form login, signed cookie, logout, private routes protected", "student_assets": "ok",
|
||
|
|
"api_docs_prefix": "ok", "six_modules": "ok", "counts": overview["counts"]}
|
||
|
|
(ROOT / "artifacts/cloud-preview-check.json").write_text(
|
||
|
|
json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||
|
|
print(json.dumps(report, ensure_ascii=True))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|