All checks were successful
Publish to PyPI Registry / publish (release) Successful in 1m14s
proof-measure is a separate, public, unauthenticated Dragonchain service. Adds: - UnauthenticatedClient: HMAC-free transport mirroring Client (session injection, allow_redirects=False, from_dict decoding). - ProofMeasureClient: get_security / report / health; default base URL https://proof-measure.dragonchain.com. Standalone (ProofMeasureClient()) and via DragonchainSDK.proof_measure. - Proof-measure dataclass models (decimals as strings, timestamps as int). - .gitea/workflows/publish.yml: build + twine upload to the Gitea PyPI registry on release (the SDK had no publish workflow before). Also fixes 3 pre-existing failing tests: _FakeSession.request didn't accept the allow_redirects kwarg the client now passes (added by the prior redirect change), so the suite was red at HEAD.
146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
"""Offline tests for the proof-measure client. No network access required."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from prime_sdk import (
|
|
DragonchainAPIError,
|
|
ProofMeasureClient,
|
|
ReportAnchorInput,
|
|
ReportRequest,
|
|
)
|
|
from prime_sdk.proof_measure import DEFAULT_BASE_URL
|
|
|
|
|
|
class _FakeResponse:
|
|
def __init__(self, status_code, text):
|
|
self.status_code = status_code
|
|
self.text = text
|
|
|
|
|
|
class _FakeSession:
|
|
def __init__(self, response):
|
|
self.response = response
|
|
self.calls = []
|
|
|
|
def request(self, method, url, data=None, headers=None, timeout=None, allow_redirects=None):
|
|
self.calls.append(
|
|
{
|
|
"method": method,
|
|
"url": url,
|
|
"data": data,
|
|
"headers": headers,
|
|
"allow_redirects": allow_redirects,
|
|
}
|
|
)
|
|
return self.response
|
|
|
|
|
|
def _client_with(session) -> ProofMeasureClient:
|
|
pm = ProofMeasureClient()
|
|
pm._client._session = session
|
|
return pm
|
|
|
|
|
|
def test_default_base_url():
|
|
assert DEFAULT_BASE_URL == "https://proof-measure.dragonchain.com"
|
|
assert ProofMeasureClient()._client.endpoint() == DEFAULT_BASE_URL
|
|
|
|
|
|
def test_get_security_builds_path_and_parses():
|
|
body = json.dumps(
|
|
{
|
|
"network": "BTC",
|
|
"consensus": "pow",
|
|
"raw": {"value": "71.63", "unit": "Yottahashes", "base": "7.16e25"},
|
|
"valueUsd": "13928170.26",
|
|
"valueUsdFormatted": "$13,928,170.26",
|
|
"label": "$13,928,170.26 energy consumed",
|
|
"normalizedScore": "0.9234",
|
|
"since": 1780504894,
|
|
"asOf": 1780591253,
|
|
}
|
|
)
|
|
fake = _FakeSession(_FakeResponse(200, body))
|
|
res = _client_with(fake).get_security("BTC", 1780504894)
|
|
|
|
call = fake.calls[0]
|
|
assert call["method"] == "GET"
|
|
assert call["url"] == DEFAULT_BASE_URL + "/api/v1/security/BTC?since=1780504894"
|
|
assert call["allow_redirects"] is False
|
|
# no auth headers on the unauthenticated client
|
|
assert "Authorization" not in call["headers"]
|
|
assert res.network == "BTC"
|
|
assert res.consensus == "pow"
|
|
assert res.raw.unit == "Yottahashes"
|
|
assert res.value_usd == "13928170.26"
|
|
assert res.value_usd_formatted == "$13,928,170.26"
|
|
assert res.since == 1780504894 and res.as_of == 1780591253
|
|
|
|
|
|
def test_get_security_omits_since_when_not_positive():
|
|
fake = _FakeSession(_FakeResponse(200, json.dumps({"network": "ETH", "consensus": "pos"})))
|
|
_client_with(fake).get_security("ETH")
|
|
assert fake.calls[0]["url"] == DEFAULT_BASE_URL + "/api/v1/security/ETH"
|
|
|
|
|
|
def test_report_posts_request_and_parses():
|
|
resp_body = json.dumps(
|
|
{
|
|
"transactionId": "tx-1",
|
|
"primeId": "p-1",
|
|
"blockId": "42",
|
|
"anchors": [
|
|
{
|
|
"network": "BTC",
|
|
"anchorTimestamp": 1712345678,
|
|
"anchorTxHash": "0xabc",
|
|
"security": {"network": "BTC", "consensus": "pow", "valueUsd": "100.00"},
|
|
}
|
|
],
|
|
"totalValueUsd": "100.00",
|
|
"totalValueUsdFormatted": "$100.00",
|
|
"hashPower": {"value": "40.06", "units": "Yottahashes"},
|
|
"totalNormalizedScore": "0.9",
|
|
}
|
|
)
|
|
fake = _FakeSession(_FakeResponse(200, resp_body))
|
|
req = ReportRequest(
|
|
transaction_id="tx-1",
|
|
prime_id="p-1",
|
|
block_id="42",
|
|
anchors=[ReportAnchorInput(tx_hash="0xabc", timestamp=1712345678, network="BTC")],
|
|
)
|
|
rep = _client_with(fake).report(req)
|
|
|
|
call = fake.calls[0]
|
|
assert call["method"] == "POST"
|
|
assert call["url"] == DEFAULT_BASE_URL + "/api/v1/report"
|
|
assert call["headers"]["Content-Type"] == "application/json"
|
|
sent = json.loads(call["data"])
|
|
assert sent == {
|
|
"transactionId": "tx-1",
|
|
"primeId": "p-1",
|
|
"blockId": "42",
|
|
"anchors": [{"txHash": "0xabc", "timestamp": 1712345678, "network": "BTC"}],
|
|
}
|
|
assert rep.total_value_usd_formatted == "$100.00"
|
|
assert rep.hash_power is not None and rep.hash_power.units == "Yottahashes"
|
|
assert len(rep.anchors) == 1 and rep.anchors[0].security.network == "BTC"
|
|
|
|
|
|
def test_health_parses():
|
|
fake = _FakeSession(_FakeResponse(200, json.dumps({"status": "ok"})))
|
|
res = _client_with(fake).health()
|
|
assert fake.calls[0]["url"] == DEFAULT_BASE_URL + "/api/v1/health"
|
|
assert res.status == "ok"
|
|
|
|
|
|
def test_error_status_raises():
|
|
fake = _FakeSession(_FakeResponse(400, " bad request "))
|
|
with pytest.raises(DragonchainAPIError) as exc:
|
|
_client_with(fake).health()
|
|
assert exc.value.status_code == 400
|
|
assert exc.value.body == "bad request"
|