aere-cloud-sdk/python/test_aere_cloud.py
Liviu af565d3aec Install from this repository (the clients are not on npm or PyPI yet); Python and Java clients 1.5.1/1.5.0 added
The README told readers to run npm install @aere/cloud and pip install aere-cloud; neither name exists on those registries, so the
command failed, and a name there that looks like ours is not ours. Install now points at this repository for JavaScript, Python
(python/, subdirectory install) and Go (go/); java/ builds from source with the JDK alone.
2026-09-25 11:02:13 +03:00

244 lines
12 KiB
Python

"""Testele aere-cloud (Python), oglinda celor din sdk/test/sdk.test.mjs.
python3 test_aere_cloud.py unitare, cu transport fals, fara retea
AERE_KEY=ak2800... python3 test_aere_cloud.py --live + 3 probe prin productie
"""
import hashlib
import hmac as _hmac
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from aere_cloud import AereCloud, AereCloudError, verify_webhook # noqa: E402
trecute, picate = 0, 0
def proba(nume, fn):
global trecute, picate
try:
fn()
trecute += 1
print(f" OK {nume}")
except Exception as e:
picate += 1
print(f" PICA {nume}: {e!r}")
class Transport:
"""Opener fals: inregistreaza cererile si raspunde din scenariu."""
def __init__(self, raspunsuri):
self.cereri = []
self.raspunsuri = list(raspunsuri)
def __call__(self, cerere, timeout):
self.cereri.append(cerere)
status, corp = self.raspunsuri.pop(0)
return status, json.dumps(corp).encode("utf-8")
def t_constructor():
try:
AereCloud("")
raise AssertionError("nu a ridicat pe cheie goala")
except ValueError:
pass
def t_antet():
tr = Transport([(200, {"ok": True}), (200, {"ok": True})])
c = AereCloud("ak2800.0xAB.secret", opener=tr)
c.health()
assert "x-api-key" not in {k.lower() for k in tr.cereri[0].headers}, "health nu trimite cheia"
c.account()
assert tr.cereri[1].get_header("X-api-key") == "ak2800.0xAB.secret", "account trimite cheia"
def t_rpc():
tr = Transport([(200, {"jsonrpc": "2.0", "id": 1, "result": "0x10"})])
c = AereCloud("k", opener=tr)
assert c.rpc("eth_blockNumber") == "0x10"
corp = json.loads(tr.cereri[0].data.decode())
assert corp["method"] == "eth_blockNumber" and corp["jsonrpc"] == "2.0"
tr2 = Transport([(200, {"error": {"code": -32601, "message": "nope"}})])
try:
AereCloud("k", opener=tr2).rpc("qbft_x")
raise AssertionError("eroarea RPC nu s-a ridicat")
except AereCloudError as e:
assert e.status == 200
def t_eroare_http():
tr = Transport([(403, {"error": "invalid_or_expired_key"})])
try:
AereCloud("k", opener=tr).account()
raise AssertionError("403 nu s-a ridicat")
except AereCloudError as e:
assert e.status == 403 and e.body["error"] == "invalid_or_expired_key" and e.path == "/account"
def t_query():
tr = Transport([(200, {}), (200, {}), (200, {})])
c = AereCloud("k", opener=tr)
c.anchors(3); c.transfers("0xAB", 7); c.anchor(15000000)
assert tr.cereri[0].full_url.endswith("/data/anchors?limit=3")
assert tr.cereri[1].full_url.endswith("/data/transfers?address=0xAB&limit=7")
assert tr.cereri[2].full_url.endswith("/data/anchors/15000000")
def t_pq_sponsor():
tr = Transport([(200, {"txHash": "0xab", "account": "0xcd", "alreadyDeployed": False})])
c = AereCloud("k", opener=tr)
d = c.sponsor_create_pq_account("0x09" + "00" * 896, salt=5)
corp = json.loads(tr.cereri[0].data.decode())
assert corp["salt"] == 5 and corp["falconPubKey"].startswith("0x09")
assert tr.cereri[0].full_url.endswith("/sponsor/createPqAccount")
assert d["account"] == "0xcd"
def t_webhook():
secret, corp = "sssh", b'{"type":"pq-anchor","x":1}'
semnatura = _hmac.new(secret.encode(), corp, hashlib.sha256).hexdigest()
assert verify_webhook(corp, semnatura, secret) is True
assert verify_webhook(corp, semnatura[:-1] + ("0" if semnatura[-1] != "0" else "1"), secret) is False
assert verify_webhook(corp, semnatura + "0", secret) is False
assert verify_webhook(corp + b" ", semnatura, secret) is False
proba("constructorul cere cheia", t_constructor)
proba("cheia merge in antet, health e fara cheie", t_antet)
proba("rpc impacheteaza corect si ridica eroarea RPC", t_rpc)
proba("un status de eroare devine AereCloudError cu corpul serverului", t_eroare_http)
proba("rutele de date construiesc corect query-ul", t_query)
proba("sponsor_create_pq_account: corp si cale corecte", t_pq_sponsor)
proba("verify_webhook: buna trece; stricata, prelungita, corp atins pica", t_webhook)
def t_proof_readiness():
# 2026-09-17: proof(hash) cere cheia; readiness() e libera si trimite cheia doar cu attest=True; corpul e exact
h = "0x" + "ab" * 32
tr = Transport([
(200, {"hash": h, "notarized": True, "finality": "post-quantum"}),
(200, {"domain": "aere.network", "score": 75}),
(200, {"domain": "aere.network", "score": 75, "attestation": {"reportHash": h}}),
(200, {"domain": "aere.network", "score": 75, "cached": True}),
])
c = AereCloud("ak2800.0xAB.secret", opener=tr)
assert c.proof(h)["finality"] == "post-quantum"
assert tr.cereri[0].get_header("X-api-key") == "ak2800.0xAB.secret", "proof cere cheia"
assert tr.cereri[0].full_url.endswith("/proof/" + h)
c.readiness("aere.network")
assert "x-api-key" not in {k.lower() for k in tr.cereri[1].headers}, "scanarea libera nu trimite cheia"
assert json.loads(tr.cereri[1].data.decode()) == {"domain": "aere.network"}
assert c.readiness("aere.network", attest=True, fresh=True)["attestation"]["reportHash"] == h
assert tr.cereri[2].get_header("X-api-key") == "ak2800.0xAB.secret", "atestarea trimite cheia"
assert json.loads(tr.cereri[2].data.decode()) == {"domain": "aere.network", "fresh": True, "attest": True}
assert c.readiness_report("aere.network")["cached"] is True
assert "x-api-key" not in {k.lower() for k in tr.cereri[3].headers}
proba("proof + readiness: ruta, cheia doar cand se cere, corpul corect", t_proof_readiness)
def t_perimetru():
# 2026-09-18: readiness_perimeter() cere cheia; corpul poarta doar ce s-a cerut; discover=True e implicitul si nu se trimite
tr = Transport([(200, {"domain": "example.com", "summary": {"measured": 3, "complete": True}}), (200, {"domain": "example.com", "summary": {"complete": True}})])
c = AereCloud("ak2800.0xAB.secret", opener=tr)
assert c.readiness_perimeter("example.com")["summary"]["measured"] == 3
assert tr.cereri[0].get_header("X-api-key") == "ak2800.0xAB.secret", "perimetrul cere cheia"
assert tr.cereri[0].full_url.endswith("/pq/readiness/perimeter")
assert json.loads(tr.cereri[0].data.decode()) == {"domain": "example.com"}
c.readiness_perimeter("example.com", hosts=["api", "mail.example.com"], discover=False, attest=True)
assert json.loads(tr.cereri[1].data.decode()) == {"domain": "example.com", "hosts": ["api", "mail.example.com"], "discover": False, "attest": True}
from aere_cloud import __version__
import re as _re, pathlib as _pl
proiect = (_pl.Path(__file__).parent / "pyproject.toml").read_text(encoding="utf-8")
assert _re.search(r'^version = "' + _re.escape(__version__) + '"', proiect, _re.M), "__version__ si pyproject.toml spun versiuni diferite (platit 2026-09-18: 1.1.0 fata de 1.2.1)"
proba("readiness_perimeter: ruta cu cheie, corpul exact; __version__ = versiunea din pyproject", t_perimetru)
def t_export_audit():
# 2026-09-18: audit_export() intoarce octetii asa cum au venit si sha256-ul LOR, calculat local; cu cheie; un 400 se arunca
import hashlib
corp = b'{"t":"2026-09-17T10:00:00.000Z","m":"GET","p":"/v1/account","s":200}\n'
class TransportBrut(Transport):
def __call__(self, cerere, timeout):
self.cereri.append(cerere); status, d = self.raspunsuri.pop(0)
return status, d
tr = TransportBrut([(200, corp), (400, b'{"error":"bad_format"}')])
c = AereCloud("ak2800.0xAB.secret", opener=tr)
e = c.audit_export(day="2026-09-17")
assert e["bytes"] == corp and e["entries"] == 1 and e["sha256"] == "0x" + hashlib.sha256(corp).hexdigest()
assert tr.cereri[0].full_url.endswith("/account/audit?format=jsonl&day=2026-09-17"), tr.cereri[0].full_url
assert tr.cereri[0].get_header("X-api-key") == "ak2800.0xAB.secret"
try:
c.audit_export(fmt="xml"); raise AssertionError("un 400 trebuia aruncat")
except AereCloudError as x:
assert x.status == 400
proba("audit_export: octetii zilei si sha256-ul lor calculat local; 400 aruncat", t_export_audit)
def t_retentie_audit():
# 2026-09-18: audit_retention() citeste cu cheia; audit_verify_kept(): exportul pastrat fata de ziua tinuta si fata de PIATRA unei zile sterse (410)
import hashlib, json as _j
corp = b'{"t":"2026-05-01T10:00:00.000Z","m":"GET","p":"/v1/account","s":200}\n'
d = "0x" + hashlib.sha256(corp).hexdigest()
piatra = _j.dumps({"error": "audit_day_expired", "expired": {"day": "2026-05-01", "digest": d, "entries": 1, "deletedAt": "2026-09-25T12:00:01.000Z"}}).encode()
class TransportBrut(Transport):
def __call__(self, cerere, timeout):
self.cereri.append(cerere); status, r = self.raspunsuri.pop(0)
return status, r
tr = TransportBrut([(200, b'{"retentionDays":90,"hold":false}'), (410, piatra), (410, piatra), (200, corp), (200, corp), (429, b'{"error":"rate_limit"}')])
c = AereCloud("ak2800.0xAB.secret", opener=tr)
assert c.audit_retention()["retentionDays"] == 90
assert tr.cereri[0].full_url.endswith("/account/audit/retention") and tr.cereri[0].get_method() == "GET" and tr.cereri[0].get_header("X-api-key") == "ak2800.0xAB.secret"
bun = c.audit_verify_kept("2026-05-01", corp)
assert (bun["state"], bun["matches"], bun["digest"], bun["deleted_at"]) == ("expired", True, d, "2026-09-25T12:00:01.000Z"), bun
stricat = bytes([corp[0] ^ 1]) + corp[1:]
assert c.audit_verify_kept("2026-05-01", stricat)["matches"] is False, "un export modificat nu are voie sa se potriveasca cu piatra"
tinut = c.audit_verify_kept("2026-05-01", corp.decode("utf-8"))
assert (tinut["state"], tinut["matches"]) == ("held", True), tinut
assert c.audit_verify_kept("2026-05-01", stricat)["matches"] is False
try:
c.audit_verify_kept("2026-05-01", corp); raise AssertionError("un 429 trebuia aruncat, nu intors ca nepotrivire")
except AereCloudError as x:
assert x.status == 429
proba("audit_retention + audit_verify_kept: piatra unei zile sterse verifica exportul pastrat; o eroare nu e o nepotrivire", t_retentie_audit)
def t_fara_redirectari():
# 1.5.1: cu transportul IMPLICIT (urllib), pe un server local real: un 302 ajunge la apelant ca AereCloudError, iar tinta din
# Location nu primeste nimic (deci nici cheia). Transportul injectat al celorlalte probe nu vede redirectarile.
import http.server, threading
vazute = []
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
vazute.append((self.path, self.headers.get("x-api-key")))
if self.path.startswith("/v1/"):
self.send_response(302); self.send_header("location", "/in-alta-parte"); self.end_headers()
else:
self.send_response(200); self.send_header("content-type", "application/json"); self.end_headers(); self.wfile.write(b"{}")
def log_message(self, *a):
pass
srv = http.server.HTTPServer(("127.0.0.1", 0), H)
fir = threading.Thread(target=srv.serve_forever, daemon=True); fir.start()
try:
c = AereCloud("ak2800.0xAB.secret", base_url=f"http://127.0.0.1:{srv.server_address[1]}/v1")
try:
c.account(); raise AssertionError("302 trebuia ridicat, nu urmat")
except AereCloudError as x:
assert x.status == 302, x.status
assert not [v for v in vazute if v[0] == "/in-alta-parte"], vazute
finally:
srv.shutdown(); srv.server_close()
proba("1.5.1: o redirectare nu e urmata cu transportul implicit; tinta din Location nu primeste cheia", t_fara_redirectari)
def _live_pq(c):
try:
c.sponsor_create_pq_account("0x07", 0)
raise AssertionError("nu a refuzat cheia scurta")
except AereCloudError as e:
assert "897" in str(e.body.get("error", "")), e.body
if "--live" in sys.argv:
cheie = os.environ.get("AERE_KEY")
if not cheie:
print(" (sar LIVE: AERE_KEY lipseste)")
else:
c = AereCloud(cheie)
def t_live_health(): assert c.health()["ok"] is True
def t_live_validatori(): assert c.validators()["count"] == 10 # al zecelea (Nethermind) din 2026-09-11
def t_live_ancora(): assert c.anchors(1)["anchors"][0]["falconSeals"] >= 6
proba("LIVE: health", t_live_health)
proba("LIVE: validators = 10", t_live_validatori)
proba("LIVE: ancora cu >=6 sigilii", t_live_ancora)
proba("LIVE: sponsor PQ refuza JSON pe cheie scurta", lambda: _live_pq(c))
print(f"\n{trecute} treceri, {picate} esecuri")
sys.exit(1 if picate else 0)