Skip to content

Commit f2ab839

Browse files
committed
Added tests for the server
1 parent 91c6eb9 commit f2ab839

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import socket
5+
import subprocess
6+
import threading
7+
import time
8+
import urllib.request
9+
from pathlib import Path
10+
from typing import Any
11+
12+
import pytest
13+
from stellar_sdk import Account, Keypair, Network, TransactionBuilder
14+
15+
from komet_node.server import StellarRpcServer
16+
17+
EMPTY_CONTRACT_WAT = (Path(__file__).parent / 'data' / 'wasm' / 'empty.wat').resolve(strict=True)
18+
19+
20+
def wat_to_wasm(wat_path: Path) -> bytes:
21+
proc_res = subprocess.run(['wat2wasm', str(wat_path), '--output=/dev/stdout'], check=True, capture_output=True)
22+
return proc_res.stdout
23+
24+
25+
def _find_free_port() -> int:
26+
with socket.socket() as s:
27+
s.bind(('', 0))
28+
return s.getsockname()[1]
29+
30+
31+
def _wait_for_server(host: str, port: int, timeout: float = 10.0) -> None:
32+
deadline = time.time() + timeout
33+
while time.time() < deadline:
34+
try:
35+
with socket.create_connection((host, port), timeout=0.1):
36+
return
37+
except OSError:
38+
time.sleep(0.05)
39+
raise TimeoutError(f'Server did not start on {host}:{port}')
40+
41+
42+
def _rpc(port: int, method: str, params: dict[str, Any]) -> dict[str, Any]:
43+
body = json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params}).encode()
44+
req = urllib.request.Request(
45+
f'http://localhost:{port}',
46+
data=body,
47+
headers={'Content-Type': 'application/json'},
48+
)
49+
with urllib.request.urlopen(req) as resp:
50+
return json.loads(resp.read())
51+
52+
53+
@pytest.fixture
54+
def server(tmp_path: Path):
55+
port = _find_free_port()
56+
srv = StellarRpcServer(
57+
host='localhost',
58+
port=port,
59+
state_file=tmp_path / 'state.kore',
60+
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
61+
)
62+
thread = threading.Thread(target=srv.serve, daemon=True)
63+
thread.start()
64+
_wait_for_server('localhost', port)
65+
yield srv
66+
srv.shutdown()
67+
68+
69+
def test_get_health(server: StellarRpcServer) -> None:
70+
result = _rpc(server.port(), 'getHealth', {})
71+
assert result['result'] == {'status': 'healthy'}
72+
73+
74+
def test_get_network(server: StellarRpcServer) -> None:
75+
result = _rpc(server.port(), 'getNetwork', {})
76+
assert result['result']['passphrase'] == Network.TESTNET_NETWORK_PASSPHRASE
77+
assert result['result']['protocolVersion'] == '22'
78+
79+
80+
def test_get_latest_ledger_initial(server: StellarRpcServer) -> None:
81+
result = _rpc(server.port(), 'getLatestLedger', {})
82+
assert result['result']['sequence'] == 0
83+
84+
85+
def test_get_transaction_not_found(server: StellarRpcServer) -> None:
86+
result = _rpc(server.port(), 'getTransaction', {'hash': '0' * 64})
87+
assert result['result']['status'] == 'NOT_FOUND'
88+
89+
90+
def test_send_transaction_and_get_result(server: StellarRpcServer) -> None:
91+
"""Send a CreateAccount transaction through the HTTP server and poll for the result."""
92+
keypair = Keypair.random()
93+
account = Account(keypair.public_key, sequence=0)
94+
95+
envelope = (
96+
TransactionBuilder(account, Network.TESTNET_NETWORK_PASSPHRASE)
97+
.append_create_account_op(destination=keypair.public_key, starting_balance='1000')
98+
.set_timeout(30)
99+
.build()
100+
)
101+
envelope.sign(keypair)
102+
xdr_str = envelope.to_xdr()
103+
104+
# sendTransaction always returns PENDING
105+
send_result = _rpc(server.port(), 'sendTransaction', {'transaction': xdr_str})
106+
assert send_result['result']['status'] == 'PENDING'
107+
tx_hash = send_result['result']['hash']
108+
109+
# since krun runs synchronously, the result is already stored
110+
get_result = _rpc(server.port(), 'getTransaction', {'hash': tx_hash})
111+
assert get_result['result']['status'] == 'SUCCESS'
112+
assert get_result['result']['envelopeXdr'] == xdr_str
113+
114+
115+
def test_ledger_seq_increments(server: StellarRpcServer) -> None:
116+
"""ledger_seq increments by 1 for each successful transaction."""
117+
keypair = Keypair.random()
118+
account = Account(keypair.public_key, sequence=0)
119+
120+
def send_create_account() -> None:
121+
envelope = (
122+
TransactionBuilder(account, Network.TESTNET_NETWORK_PASSPHRASE)
123+
.append_create_account_op(destination=keypair.public_key, starting_balance='1000')
124+
.set_timeout(30)
125+
.build()
126+
)
127+
envelope.sign(keypair)
128+
_rpc(server.port(), 'sendTransaction', {'transaction': envelope.to_xdr()})
129+
130+
send_create_account()
131+
assert _rpc(server.port(), 'getLatestLedger', {})['result']['sequence'] == 1
132+
133+
send_create_account()
134+
assert _rpc(server.port(), 'getLatestLedger', {})['result']['sequence'] == 2
135+
136+
137+
def test_full_lifecycle_over_http(server: StellarRpcServer) -> None:
138+
"""Full contract lifecycle through the HTTP server: account → upload → deploy → invoke."""
139+
keypair = Keypair.random()
140+
account = Account(keypair.public_key, sequence=0)
141+
142+
def send(envelope_xdr: str) -> dict[str, Any]:
143+
send_res = _rpc(server.port(), 'sendTransaction', {'transaction': envelope_xdr})
144+
assert send_res['result']['status'] == 'PENDING'
145+
tx_hash = send_res['result']['hash']
146+
get_res = _rpc(server.port(), 'getTransaction', {'hash': tx_hash})
147+
assert get_res['result']['status'] == 'SUCCESS', f"Transaction failed: {get_res}"
148+
return get_res['result']
149+
150+
def builder() -> TransactionBuilder:
151+
return TransactionBuilder(account, Network.TESTNET_NETWORK_PASSPHRASE)
152+
153+
def sign_and_xdr(tb: TransactionBuilder) -> str:
154+
env = tb.set_timeout(30).build()
155+
env.sign(keypair)
156+
return env.to_xdr()
157+
158+
# 1. Create account
159+
send(sign_and_xdr(builder().append_create_account_op(keypair.public_key, '1000')))
160+
161+
# 2. Upload wasm
162+
wasm_bytecode = wat_to_wasm(EMPTY_CONTRACT_WAT)
163+
send(sign_and_xdr(builder().append_upload_contract_wasm_op(wasm_bytecode)))
164+
165+
# 3. Deploy contract
166+
from stellar_sdk.utils import sha256
167+
wasm_hash = sha256(wasm_bytecode)
168+
salt = b'\x00' * 32
169+
send(sign_and_xdr(builder().append_create_contract_op(wasm_hash, keypair.public_key, None, salt)))
170+
171+
# 4. Invoke foo()
172+
contract_address = server.interpreter.contract_address_from_deployer_address(keypair.public_key, salt)
173+
send(sign_and_xdr(builder().append_invoke_contract_function_op(contract_address, 'foo', [])))

0 commit comments

Comments
 (0)