feat: sync vehicle monitoring and accumulated platform updates
Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates. Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Black-box shared-token acceptance against the loopback-only fixture launcher."""
|
||||
import argparse
|
||||
import http.cookiejar
|
||||
import ipaddress
|
||||
import json
|
||||
from pathlib import Path
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("fixture_config", type=Path)
|
||||
args = parser.parse_args()
|
||||
cfg = json.loads(args.fixture_config.read_text())
|
||||
origins = (cfg["publicOrigin"], cfg["baseURL"])
|
||||
for value in origins:
|
||||
parsed = urllib.parse.urlsplit(value)
|
||||
assert parsed.scheme == "http" and ipaddress.ip_address(parsed.hostname).is_loopback
|
||||
assert not parsed.username and not parsed.query and not parsed.fragment and not parsed.path
|
||||
platform, upstream = origins
|
||||
jar = http.cookiejar.CookieJar()
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), urllib.request.HTTPCookieProcessor(jar), NoRedirect())
|
||||
|
||||
def request(origin, path, body=None, credential=None, headers=None, form=False, method=None):
|
||||
assert origin in origins and path.startswith("/") and not path.startswith("//")
|
||||
h = {"Origin": platform}
|
||||
if credential:
|
||||
h.update({"Authorization": "Bearer " + credential["accessToken"], "X-OneOS-Client-ID": credential["clientId"]})
|
||||
if headers: h.update(headers)
|
||||
data = None
|
||||
if body is not None:
|
||||
h["Content-Type"] = "application/x-www-form-urlencoded" if form else "application/json"
|
||||
data = (urllib.parse.urlencode(body) if form else json.dumps(body)).encode()
|
||||
req = urllib.request.Request(origin + path, data=data, headers=h, method=method)
|
||||
try: resp = opener.open(req, timeout=5)
|
||||
except urllib.error.HTTPError as error: resp = error
|
||||
with resp:
|
||||
raw = resp.read()
|
||||
result = json.loads(raw) if "json" in resp.headers.get("Content-Type", "") else None
|
||||
return resp.code, result, resp.headers
|
||||
|
||||
passed = []
|
||||
def check(name, condition):
|
||||
assert condition, name
|
||||
passed.append(name)
|
||||
print("PASS " + name, flush=True)
|
||||
|
||||
def login(name):
|
||||
status, body, _ = request(platform, "/api/v2/auth/oneos/login", {"username": name, "password": "LocalOnly123!"})
|
||||
assert status == 200, "local fixture login failed"
|
||||
return body["data"]
|
||||
|
||||
def control(name, action):
|
||||
status, _, _ = request(upstream, "/__local/control", {"username": name, "action": action}, headers={"Authorization": "Bearer " + cfg["serviceToken"]})
|
||||
assert status == 200
|
||||
|
||||
alpha, beta = login("alpha"), login("beta")
|
||||
status, identity, _ = request(upstream, "/system/user/getInfo", headers={"Authorization": "Bearer " + alpha["accessToken"], "ClientID": alpha["clientId"]})
|
||||
check("platform-returned token works unchanged on OneOS", status == 200 and identity["data"]["user"]["userName"] == "alpha")
|
||||
status, me, _ = request(platform, "/api/v2/auth/oneos/me", credential=alpha)
|
||||
check("platform invokes live OneOS as current user", status == 200 and me["data"]["username"] == "alpha")
|
||||
_, a, _ = request(platform, "/api/vehicles", credential=alpha)
|
||||
_, b, _ = request(platform, "/api/vehicles", credential=beta)
|
||||
check("two user vehicle lists remain disjoint", {v["vin"] for v in a["data"]["items"]} == {"LB9A32A24R0LS1426"} and {v["vin"] for v in b["data"]["items"]} == {"LNXNEGRR7SR318212"})
|
||||
status, _, _ = request(platform, "/api/v2/tracks?vin=LNXNEGRR7SR318212", credential=alpha)
|
||||
check("explicit cross-user VIN rejected", status == 403)
|
||||
status, _, _ = request(platform, "/api/v2/admin/users", credential=alpha)
|
||||
check("ungranted management API rejected without relogin", status == 403)
|
||||
status, _, _ = request(platform, "/api/v2/session", credential=alpha)
|
||||
check("forbidden route did not revoke valid token", status == 200)
|
||||
status, _, _ = request(platform, "/api/v2/session", credential={**alpha, "clientId": "wrong-client"})
|
||||
check("wrong OneOS client ID rejected", status == 401)
|
||||
status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "noaccess", "password": "LocalOnly123!"})
|
||||
check("valid OneOS identity without entry permission denied", status == 403)
|
||||
status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "alpha", "password": "wrong-password"})
|
||||
check("wrong password denied without local fallback", status == 401)
|
||||
status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "alpha", "password": "LocalOnly123!"}, headers={"Origin": "http://127.0.0.1:29999"})
|
||||
check("cross-origin login rejected", status == 403)
|
||||
|
||||
status, _, _ = request(upstream, "/portal/login", {"username": "alpha", "password": "LocalOnly123!"}, form=True)
|
||||
assert status == 303
|
||||
portal_token = next(cookie.value for cookie in jar if cookie.name == "fixture_oneos")
|
||||
status, _, hdr = request(upstream, "/portal/ticket", {})
|
||||
assert status == 303
|
||||
callback = urllib.parse.urlsplit(hdr["Location"])
|
||||
assert urllib.parse.urlunsplit((callback.scheme, callback.netloc, "", "", "")) == platform
|
||||
code = urllib.parse.parse_qs(callback.query)["code"][0]
|
||||
status, _, hdr = request(platform, callback.path + "?" + callback.query)
|
||||
check("5-second ticket redeemed before SPA with clean redirect", status == 303 and hdr["Location"] == "/auth/oneos/complete" and "HttpOnly" in hdr.get("Set-Cookie", ""))
|
||||
status, pending, _ = request(platform, "/api/v2/auth/oneos/pending")
|
||||
check("SSO staged identity has no exposed access token", status == 200 and "accessToken" not in pending["data"])
|
||||
status, _, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": "wrong"})
|
||||
check("SSO wrong CSRF rejected", status == 403)
|
||||
status, confirmed, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": pending["data"]["csrfToken"]})
|
||||
check("SSO returns original OneOS portal token byte-for-byte", status == 200 and confirmed["data"]["accessToken"] == portal_token)
|
||||
check("password and SSO map to same platform subject", confirmed["data"]["session"]["subjectId"] == alpha["session"]["subjectId"])
|
||||
status, _, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": pending["data"]["csrfToken"]})
|
||||
check("SSO confirmation replay rejected", status in (401, 403))
|
||||
status, _, _ = request(upstream, "/auth/inner/v1/sso/vehicle-platform/token-exchange", {"code": code}, headers={"Authorization": "Bearer " + cfg["serviceToken"]})
|
||||
check("consumed upstream ticket cannot be exchanged twice", status == 401)
|
||||
_, _, hdr = request(upstream, "/portal/ticket", {})
|
||||
expired = urllib.parse.urlsplit(hdr["Location"])
|
||||
time.sleep(5.1)
|
||||
status, _, hdr = request(platform, expired.path + "?" + expired.query)
|
||||
check("expired 5-second ticket denied with clean URL", status == 303 and "error=" in hdr["Location"] and "code=" not in hdr["Location"])
|
||||
|
||||
control("alpha", "revoke-vehicles")
|
||||
status, empty, _ = request(platform, "/api/vehicles", credential=alpha)
|
||||
check("vehicle revocation effective next request", status == 200 and empty["data"]["items"] == [])
|
||||
control("alpha", "restore-vehicles")
|
||||
control("alpha", "disable")
|
||||
status, _, _ = request(platform, "/api/v2/session", credential=alpha)
|
||||
check("OneOS account disable effective next request", status == 401)
|
||||
control("alpha", "enable")
|
||||
status, _, _ = request(platform, "/api/v2/auth/logout", {}, credential=beta)
|
||||
check("unified logout accepted", status == 200)
|
||||
status, _, _ = request(upstream, "/system/user/getInfo", headers={"Authorization": "Bearer " + beta["accessToken"], "ClientID": beta["clientId"]})
|
||||
check("logout revoked same token at OneOS", status == 401)
|
||||
status, _, _ = request(platform, "/api/v2/session", credential=beta)
|
||||
check("logout revoked same token at platform", status == 401)
|
||||
print(json.dumps({"passed": len(passed), "environment": "literal-loopback-only", "remoteCalls": 0}, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user