Add proof-measure client + Gitea PyPI publish; bump to 0.4.0
All checks were successful
Publish to PyPI Registry / publish (release) Successful in 1m14s
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.
This commit is contained in:
@@ -93,8 +93,16 @@ class _FakeSession:
|
||||
self.response = response
|
||||
self.calls = []
|
||||
|
||||
def request(self, method, url, data=None, headers=None, timeout=None):
|
||||
self.calls.append({"method": method, "url": url, "data": data, "headers": headers})
|
||||
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
|
||||
|
||||
|
||||
|
||||
145
tests/test_proof_measure.py
Normal file
145
tests/test_proof_measure.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user