Implement full MVP: auth, HD wallet, Electrum client, deposits, bets, round/draw engine, payout, withdrawals, RBF, admin+audit
All 10 build-order stages complete and unit-tested (49 tests). Verified live on mainnet: registration/address derivation, deposit crediting, a real 10 PLM bet (broadcast + confirmed + change credited). A full round close->draw->payout cycle was triggered live and was in progress at commit time. Withdrawal and RBF bump are unit-tested but not yet exercised against a live broadcast. Known gaps (scheduler doesn't resume mid-flight rounds after restart, payout has no retry, no deployment setup, etc.) are documented in CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from app.electrum.client import ElectrumClient, ElectrumError
|
||||
|
||||
|
||||
class FakeWriter:
|
||||
def __init__(self):
|
||||
self.written = b""
|
||||
|
||||
def write(self, data: bytes) -> None:
|
||||
self.written += data
|
||||
|
||||
async def drain(self) -> None:
|
||||
pass
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
async def wait_closed(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
async def _client_with_fake_transport() -> tuple[ElectrumClient, asyncio.StreamReader, FakeWriter]:
|
||||
client = ElectrumClient("localhost", 1234)
|
||||
reader = asyncio.StreamReader()
|
||||
writer = FakeWriter()
|
||||
client._reader = reader
|
||||
client._writer = writer
|
||||
client._read_task = asyncio.create_task(client._read_loop())
|
||||
return client, reader, writer
|
||||
|
||||
|
||||
async def test_request_resolves_on_matching_response():
|
||||
client, reader, writer = await _client_with_fake_transport()
|
||||
|
||||
task = asyncio.create_task(client.request("blockchain.headers.subscribe"))
|
||||
await asyncio.sleep(0) # let request() write the payload
|
||||
sent = json.loads(writer.written.decode())
|
||||
assert sent["method"] == "blockchain.headers.subscribe"
|
||||
|
||||
reader.feed_data((json.dumps({"id": sent["id"], "result": {"height": 100}}) + "\n").encode())
|
||||
result = await task
|
||||
assert result == {"height": 100}
|
||||
|
||||
client._read_task.cancel()
|
||||
|
||||
|
||||
async def test_error_response_raises_electrum_error():
|
||||
client, reader, writer = await _client_with_fake_transport()
|
||||
|
||||
task = asyncio.create_task(client.request("blockchain.transaction.broadcast", ["deadbeef"]))
|
||||
await asyncio.sleep(0)
|
||||
sent = json.loads(writer.written.decode())
|
||||
|
||||
reader.feed_data((json.dumps({"id": sent["id"], "error": "bad tx"}) + "\n").encode())
|
||||
try:
|
||||
await task
|
||||
assert False, "expected ElectrumError"
|
||||
except ElectrumError:
|
||||
pass
|
||||
|
||||
client._read_task.cancel()
|
||||
|
||||
|
||||
async def test_notification_delivered_to_subscription_queue():
|
||||
client, reader, writer = await _client_with_fake_transport()
|
||||
queue = client.notifications("blockchain.scripthash.subscribe")
|
||||
|
||||
push = {"method": "blockchain.scripthash.subscribe", "params": ["abcd", "newstatus"]}
|
||||
reader.feed_data((json.dumps(push) + "\n").encode())
|
||||
await asyncio.sleep(0)
|
||||
|
||||
params = await asyncio.wait_for(queue.get(), timeout=1)
|
||||
assert params == ["abcd", "newstatus"]
|
||||
|
||||
client._read_task.cancel()
|
||||
Reference in New Issue
Block a user