Skip to content

Commit 9cff231

Browse files
author
Jeff Schroeder
committed
Initial stub for unit tests
1 parent f1b08d1 commit 9cff231

File tree

4 files changed

+221
-2
lines changed

4 files changed

+221
-2
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,21 @@ Pyth Client in Python
22
=====================
33

44
A Python library to retrieve data from Pyth account structures off the Solana blockchain.
5+
6+
7+
Developer Setup
8+
---------------
9+
10+
To install this library in editable mode with test dependencies:
11+
12+
pip install -e '.[testing]'
13+
14+
To run the unit tests:
15+
16+
pytest --cov=pythclient
17+
18+
If html based test coverage is more your jam:
19+
20+
pytest --cov-report=html --cov=pythclient
21+
22+
The coverage webpages will be in the `htmlcov` directory.

setup.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
from setuptools import setup
22

3+
requirements = ['aiodns', 'aiohttp', 'backoff', 'base58', 'loguru']
4+
35
setup(
46
name='pythclient',
5-
version='0.0.1',
7+
version='0.0.2',
68
packages=['pythclient'],
79
author='Pyth Developers',
810
author_email='contact@pyth.network',
9-
install_requires=['aiodns', 'aiohttp', 'backoff', 'base58', 'loguru']
11+
install_requires=requirements,
12+
extras_require={
13+
'testing': requirements + ['pytest', 'pytest-cov'],
14+
},
1015
)

tests/test_price_account_header.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import pytest
2+
3+
from pythclient.pythaccounts import PythAccountType, _parse_header
4+
5+
6+
@pytest.fixture
7+
def valid_binary():
8+
"""
9+
magic=0xA1B2C3D4 version=2, type=price, size=16
10+
"""
11+
return bytes([212, 195, 178, 161, 2, 0, 0, 0, 3, 0, 0, 0, 16, 0, 0, 0])
12+
13+
14+
@pytest.fixture
15+
def valid_expected():
16+
return PythAccountType.PRICE, 16, 2
17+
18+
19+
@pytest.fixture
20+
def bad_magic():
21+
"""
22+
magic=0xDEADBEEF, version=2, type=price, size=16
23+
"""
24+
return bytes([239, 190, 173, 222, 2, 0, 0, 0, 3, 0, 0, 0, 16, 0, 0, 0])
25+
26+
27+
@pytest.fixture
28+
def bad_magic_message():
29+
return "Invalid Pyth account data header has wrong magic: expected a1b2c3d4, got deadbeef"
30+
31+
32+
@pytest.fixture
33+
def wrong_version():
34+
"""
35+
magic=0xA1B2C3D4 version=42, type=price, size=16
36+
"""
37+
return bytes([212, 195, 178, 161, 42, 0, 0, 0, 3, 0, 0, 0, 16, 0, 0, 0])
38+
39+
40+
@pytest.fixture
41+
def wrong_version_message():
42+
return "Invalid Pyth account data has unsupported version 42"
43+
44+
45+
@pytest.fixture
46+
def wrong_size():
47+
"""
48+
magic=0xA1B2C3D4 version=2, type=price, size=32
49+
"""
50+
return bytes([212, 195, 178, 161, 2, 0, 0, 0, 3, 0, 0, 0, 32, 0, 0, 0])
51+
52+
53+
@pytest.fixture
54+
def wrong_size_message():
55+
return "Invalid Pyth header says data is 32 bytes, but buffer only has 16 bytes"
56+
57+
58+
@pytest.fixture
59+
def too_short():
60+
"""
61+
Totally bogus messge that is too short
62+
"""
63+
return bytes([1, 2, 3, 4])
64+
65+
66+
@pytest.fixture
67+
def too_short_message():
68+
return "Pyth account data too short"
69+
70+
71+
@pytest.mark.parametrize(
72+
"buffer_fixture_name",
73+
["bad_magic", "wrong_version", "wrong_size", "too_short"],
74+
)
75+
def test_header_parsing_errors(buffer_fixture_name, request):
76+
buffer = request.getfixturevalue(buffer_fixture_name)
77+
exc_message = request.getfixturevalue(f"{buffer_fixture_name}_message")
78+
79+
with pytest.raises(ValueError, match=exc_message):
80+
_parse_header(
81+
buffer=buffer,
82+
offset=0,
83+
key="Invalid",
84+
)
85+
86+
87+
def test_header_parsing_valid(valid_binary, valid_expected):
88+
actual = _parse_header(
89+
buffer=valid_binary,
90+
offset=0,
91+
key="Invalid",
92+
)
93+
assert actual == valid_expected

tests/test_price_info.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import pytest
2+
from pythclient.pythaccounts import PythPriceStatus, PythPriceInfo
3+
4+
5+
@pytest.fixture
6+
def price_info_trading():
7+
return PythPriceInfo(
8+
raw_price=59609162000,
9+
raw_confidence_interval=43078500,
10+
price_status=PythPriceStatus.TRADING,
11+
slot=105367617,
12+
exponent=-8,
13+
)
14+
15+
16+
@pytest.fixture
17+
def price_info_trading_bytes():
18+
return bytes([
19+
16, 161, 251, 224, 13, 0, 0, 0, 100, 83, 145, 2, 0, 0,
20+
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 65, 200, 71, 6, 0, 0, 0, 0,
21+
])
22+
23+
24+
@pytest.mark.parametrize(
25+
"raw_price,raw_confidence_interval,price_status,slot,exponent,price,confidence_interval",
26+
[
27+
(
28+
1234567890,
29+
3.0,
30+
PythPriceStatus.TRADING,
31+
12345,
32+
-8,
33+
12.345678900000001,
34+
3.0000000000000004e-08,
35+
),
36+
(0, 0, PythPriceStatus.UNKNOWN, 100001, -9, 0.0, 0.0),
37+
],
38+
)
39+
class TestPythPriceInfo:
40+
def test_price_info(
41+
self,
42+
raw_price,
43+
raw_confidence_interval,
44+
price_status,
45+
slot,
46+
exponent,
47+
price,
48+
confidence_interval,
49+
):
50+
actual = PythPriceInfo(
51+
raw_price=raw_price,
52+
raw_confidence_interval=raw_confidence_interval,
53+
price_status=price_status,
54+
slot=slot,
55+
exponent=exponent,
56+
)
57+
for key, actual_value in dict(actual).items():
58+
assert actual_value == locals().get(key), f"'{key}' mismatch"
59+
60+
def test_price_info_iter(
61+
self,
62+
raw_price,
63+
raw_confidence_interval,
64+
price_status,
65+
slot,
66+
exponent,
67+
price,
68+
confidence_interval,
69+
):
70+
actual = dict(
71+
PythPriceInfo(
72+
raw_price=raw_price,
73+
raw_confidence_interval=raw_confidence_interval,
74+
price_status=price_status,
75+
slot=slot,
76+
exponent=exponent,
77+
)
78+
)
79+
expected = {
80+
"raw_price": raw_price,
81+
"raw_confidence_interval": raw_confidence_interval,
82+
"price_status": price_status,
83+
"slot": slot,
84+
"exponent": exponent,
85+
"price": price,
86+
"confidence_interval": confidence_interval,
87+
}
88+
assert actual == expected
89+
90+
91+
def test_price_info_deserialise(price_info_trading, price_info_trading_bytes):
92+
actual = PythPriceInfo.deserialise(
93+
buffer=price_info_trading_bytes,
94+
offset=0,
95+
exponent=price_info_trading.exponent,
96+
)
97+
assert dict(actual) == dict(price_info_trading)
98+
99+
100+
def test_price_info_str(price_info_trading):
101+
expected = "PythPriceInfo status PythPriceStatus.TRADING price 596.09162"
102+
assert str(price_info_trading) == expected
103+
assert repr(price_info_trading) == expected

0 commit comments

Comments
 (0)