feat: declare API errors and authentication with developer tooling
This commit is contained in:
@@ -59,9 +59,9 @@ class LimitedMySQL(MySQL):
|
||||
def exercise(binary, project, env, db, transport):
|
||||
with socket.socket() as sock:
|
||||
sock.bind(("127.0.0.1", 0)); port = sock.getsockname()[1]
|
||||
token, signing = secrets.token_hex(24), secrets.token_hex(24)
|
||||
token, signing, readToken = secrets.token_hex(24), secrets.token_hex(24), secrets.token_hex(24)
|
||||
environment = dict(env, GO_OMNI_HTTP_ADDR=f"127.0.0.1:{port}", GO_OMNI_TRANSPORT=transport,
|
||||
USER_SERVICE_API_TOKEN=token, GO_OMNI_STORAGE_SIGNING_KEY=signing,
|
||||
USER_SERVICE_API_TOKEN=token, USER_SERVICE_READ_TOKEN=readToken, GO_OMNI_STORAGE_SIGNING_KEY=signing,
|
||||
GO_OMNI_STORAGE_ROOT=str(project / (transport + "Files")),
|
||||
GO_OMNI_STORAGE_PUBLIC_URL=f"http://127.0.0.1:{port}/storage/object")
|
||||
logs = project / "var/log/server.log"
|
||||
@@ -72,7 +72,7 @@ def exercise(binary, project, env, db, transport):
|
||||
client = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
def request(method, path, body=None, expected=200, auth=True):
|
||||
headers = {"Content-Type": "application/json", "X-Request-ID": "apiWorkflow"}
|
||||
if auth: headers["Authorization"] = "Bearer " + token
|
||||
if auth: headers["Authorization"] = "Bearer " + (token if auth is True else auth)
|
||||
req = urllib.request.Request(f"http://127.0.0.1:{port}" + path,
|
||||
data=None if body is None else json.dumps(body).encode(), headers=headers, method=method)
|
||||
try: response = client.open(req, timeout=5)
|
||||
@@ -98,6 +98,10 @@ def exercise(binary, project, env, db, transport):
|
||||
process = start()
|
||||
try:
|
||||
request("GET", "/api/v1/users", expected=401, auth=False); checks.append("authBeforeService")
|
||||
request("GET", "/api/v1/users", auth=readToken)
|
||||
denied = request("POST", "/api/v1/users", {"name": "NoWrite", "email": "readonly@example.com"}, 403, auth=readToken)
|
||||
assert denied["code"] == "FORBIDDEN"
|
||||
checks.append("readOnlyPermission403")
|
||||
empty = request("GET", "/api/v1/users")["data"]
|
||||
assert empty["items"] == [] and empty["total"] == 0
|
||||
checks.append("emptyPageAndQueryDefaults")
|
||||
@@ -143,6 +147,11 @@ def exercise(binary, project, env, db, transport):
|
||||
checks.append("signedDownloadTamperRejected")
|
||||
doc = request("GET", "/openapi.json")
|
||||
assert doc == json.loads((project / "docs/openapi.json").read_text()); checks.append("openAPIMatchesContract")
|
||||
createDoc = doc["paths"]["/api/v1/users"]["post"]
|
||||
assert createDoc["security"] == [{"AdminAuth": []}]
|
||||
for status, name, code in (("401", "Unauthenticated", "UNAUTHENTICATED"), ("403", "Forbidden", "FORBIDDEN"), ("409", "EmailTaken", "EMAIL_TAKEN")):
|
||||
assert createDoc["responses"][status]["content"]["application/json"]["examples"][name]["value"]["code"] == code
|
||||
checks.append("declaredAuthAndBusinessErrorDocumentation")
|
||||
run(["docker", "stop", "--time=3", db.container], project, db.env)
|
||||
request("GET", "/health/ready", expected=503)
|
||||
run(["docker", "start", db.container], project, db.env); db.wait()
|
||||
@@ -162,7 +171,7 @@ def exercise(binary, project, env, db, transport):
|
||||
text = logs.read_text()
|
||||
entries = [json.loads(line) for line in text.splitlines() if line.startswith("{")]
|
||||
assert any(e.get("msg") == "user.created" and e.get("requestId") == "apiWorkflow" for e in entries)
|
||||
assert all(secret not in text for secret in (token, signing, env["GO_OMNI_DB_DSN"], link, "updated@example.com"))
|
||||
assert all(secret not in text for secret in (token, signing, readToken, env["GO_OMNI_DB_DSN"], link, "updated@example.com"))
|
||||
checks.append("businessFileLogsAndSecretExclusion")
|
||||
return {"transport": transport, "checks": checks}
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user