Skip to content

Commit 0f55e06

Browse files
deividaspetraitismergify[bot]
authored andcommitted
SQS-347 | integration, synthetic monitoring tests for active orders (#504)
* SQS-347 | integration, synthetic monitoring tests for active orders query Implements integration, synthetic monitoring tests for the for active orders query. Additionally adds test file for locust. (cherry picked from commit 9de1489)
1 parent d16acb9 commit 0f55e06

7 files changed

+182
-33
lines changed

locust/locustfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
from token_prices import TokenPrices
55
from in_given_out import ExactAmountOutQuote
66
from out_given_in import ExactAmountInQuote
7-
from orderbook_active_orders import OrderbookActiveOrders
87
from passthrough_portfolio_balances import PassthroughPortfolioBalances
8+
from passthrough_orderbook_active_orders import PassthroughOrderbookActiveOrders

locust/orderbook_active_orders.py

-18
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from locust import HttpUser, task
2+
3+
# burner address for the integration tests
4+
addr = "osmo1jgz4xmaw9yk9pjxd4h8c2zs0r0vmgyn88s8t6l"
5+
6+
7+
class PassthroughOrderbookActiveOrders(HttpUser):
8+
@task
9+
def passthrough_orderbook_active_orders(self):
10+
self.client.get(f"/passthrough/active-orders?userOsmoAddress={addr}")
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from decimal import Decimal
2+
from typing import List
3+
import time
4+
from datetime import datetime
5+
6+
7+
class LimitOrderAsset:
8+
def __init__(self, symbol: str):
9+
self.symbol = symbol
10+
11+
def validate(self):
12+
# Ensure symbol is not empty
13+
assert self.symbol, "Symbol must not be empty"
14+
15+
class LimitOrder:
16+
def __init__(self, tick_id: int, order_id: int, order_direction: str, owner: str, quantity: str, etas: str,
17+
claim_bounty: str, placed_quantity: str, placed_at: int, price: str, percentClaimed: str,
18+
totalFilled: str, percentFilled: str, orderbookAddress: str, status: str, output: str,
19+
quote_asset: dict, base_asset: dict):
20+
self.tick_id = int(tick_id)
21+
self.order_id = int(order_id)
22+
self.order_direction = order_direction
23+
self.owner = owner
24+
self.quantity = Decimal(quantity)
25+
self.etas = Decimal(etas)
26+
self.claim_bounty = Decimal(claim_bounty)
27+
self.placed_quantity = Decimal(placed_quantity)
28+
self.placed_at = int(placed_at)
29+
self.price = Decimal(price)
30+
self.percent_claimed = Decimal(percentClaimed) # Changed variable name
31+
self.total_filled = Decimal(totalFilled)
32+
self.percent_filled = Decimal(percentFilled)
33+
self.orderbook_address = orderbookAddress
34+
self.status = status
35+
self.output = Decimal(output)
36+
self.quote_asset = LimitOrderAsset(**quote_asset)
37+
self.base_asset = LimitOrderAsset(**base_asset)
38+
39+
def validate(self, owner_address=None):
40+
# Check if order_id is non-negative
41+
assert self.order_id >= 0, f"Order ID {self.order_id} cannot be negative"
42+
43+
# Check if order_direction is either "bid" or "ask"
44+
assert self.order_direction in ['bid', 'ask'], f"Order direction {self.order_direction} must be 'bid' or 'ask'"
45+
46+
# Validate owner address (Osmosis address format)
47+
assert self.owner == owner_address, f"Owner address {self.owner} is invalid"
48+
49+
# Check if quantity is non-negative
50+
assert self.quantity > 0, f"Quantity {self.quantity} cannot be negative"
51+
52+
# Check if claim_bounty is non-negative
53+
assert self.claim_bounty > 0, f"Claim bounty {self.claim_bounty} cannot be negative"
54+
55+
# Validate placed_quantity is non-negative
56+
assert self.placed_quantity > 0, f"Placed quantity {self.placed_quantity} cannot be negative"
57+
58+
# Validate placed_at is a valid Unix timestamp
59+
assert 0 <= self.placed_at <= int(time.time()), f"Placed_at timestamp {self.placed_at} is invalid"
60+
61+
# Check if price is positive
62+
assert self.price > 0, f"Price {self.price} must be positive"
63+
64+
# Check if percent_claimed is between 0 and 100
65+
assert 0 <= self.percent_claimed <= 100, f"Percent claimed {self.percent_claimed} must be between 0 and 100"
66+
67+
# Check if total_filled is non-negative
68+
assert self.total_filled >= 0, f"Total filled {self.total_filled} cannot be negative"
69+
70+
# Check if percent_filled is between 0 and 100
71+
assert 0 <= self.percent_filled <= 100, f"Percent filled {self.percent_filled} must be between 0 and 100"
72+
73+
# Ensure status is not empty
74+
assert self.status, "Status must not be empty"
75+
76+
# Ensure orderbook_address is not empty
77+
assert self.orderbook_address, "Orderbook address must not be empty"
78+
79+
# Check if output is non-negative
80+
assert self.output >= 0, f"Output {self.output} cannot be negative"
81+
82+
# Validate quote_asset
83+
self.quote_asset.validate()
84+
85+
# Validate base_asset
86+
self.base_asset.validate()
87+
88+
@staticmethod
89+
def _is_valid_unix_timestamp(timestamp):
90+
try:
91+
datetime.utcfromtimestamp(int(timestamp))
92+
return True
93+
except (ValueError, OverflowError):
94+
return False
95+
96+
97+
class OrderbookActiveOrdersResponse:
98+
def __init__(self, orders: List[dict], is_best_effort: bool):
99+
self.orders = [LimitOrder(**order) for order in orders]
100+
self.is_best_effort = is_best_effort
101+
102+
def validate(self, owner_address):
103+
# Validate each order
104+
order_ids = set()
105+
for order in self.orders:
106+
order.validate(owner_address)
107+
108+
# Ensure order_id is unique
109+
if order.order_id in order_ids:
110+
raise ValueError(f"Duplicate order_id found: {order.order_id}")
111+
order_ids.add(order.order_id)

tests/sqs_service.py

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
CANONICAL_ORDERBOOKS_URL = "/pools/canonical-orderbooks"
1616

1717
PASSTHROUGH_PORTFOLIO_ASSETS = "/passthrough/portfolio-assets/"
18+
PASSTHROUGH_ACTIVE_ORDERBOOK_ORDERS = "/passthrough/active-orders"
1819

1920
CONFIG_URL = "/config"
2021

@@ -240,6 +241,19 @@ def get_canonical_orderbooks(self):
240241

241242
return response.json()
242243

244+
245+
def get_active_orderbook_orders(self, address):
246+
"""
247+
Fetches active orderbook orders from the specified endpoint and address and returns them.
248+
"""
249+
250+
response = requests.get(self.url + f"{PASSTHROUGH_ACTIVE_ORDERBOOK_ORDERS}?userOsmoAddress={address}", headers=self.headers)
251+
252+
if response.status_code != 200:
253+
raise Exception(f"Error fetching active orderbook orders: {response.text}")
254+
255+
return response.json()
256+
243257
def get_portfolio_assets(self, address):
244258
"""
245259
Fetches the portfolio assets from the specified endpoint and address and returns them.

tests/test_passthrough.py

+41-13
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,63 @@
1+
import time
12
import conftest
23
import pytest
34

45
from sqs_service import *
5-
import util
6+
from active_orderbook_orders_response import OrderbookActiveOrdersResponse
67
from conftest import SERVICE_MAP
78
from e2e_math import *
89
from decimal import *
910

11+
# Arbitrary choice based on performance at the time of test writing
12+
EXPECTED_LATENCY_UPPER_BOUND_MS = 150
1013

11-
user_balances_assets_category_name = "user-balances"
12-
unstaking_assets_category_name = "unstaking"
13-
staked_assets_category_name = "staked"
14-
inLocks_assets_category_name = "in-locks"
15-
pooled_assets_category_name = "pooled"
16-
unclaimed_rewards_assets_category_name = "unclaimed-rewards"
17-
total_assets_category_name = "total-assets"
14+
user_balances_assets_category_name = "user-balances"
15+
unstaking_assets_category_name = "unstaking"
16+
staked_assets_category_name = "staked"
17+
inLocks_assets_category_name = "in-locks"
18+
pooled_assets_category_name = "pooled"
19+
unclaimed_rewards_assets_category_name = "unclaimed-rewards"
20+
total_assets_category_name = "total-assets"
1821

1922
# Test suite for the /passthrough endpoint
2023

2124
# Note: this is for convinience to skip long-running tests in development
2225
# locally.
2326
# @pytest.mark.skip(reason="This test is currently disabled")
24-
class TestPassthrough:
2527

28+
29+
class TestPassthrough:
2630
def test_poortfolio_assets(self, environment_url):
2731
run_test_portfolio_assets(environment_url)
2832

33+
def test_active_orderbook_orders(self, environment_url):
34+
run_test_active_orderbook_orders(environment_url)
35+
36+
37+
def run_test_active_orderbook_orders(environment_url):
38+
sqs_service = SERVICE_MAP[environment_url]
39+
40+
# list of burner addresses for the integration tests
41+
addresses = [
42+
"osmo1jgz4xmaw9yk9pjxd4h8c2zs0r0vmgyn88s8t6l",
43+
]
44+
45+
for address in addresses:
46+
47+
start_time = time.time()
48+
response = sqs_service.get_active_orderbook_orders(address)
49+
elapsed_time_ms = (time.time() - start_time) * 1000
50+
51+
assert EXPECTED_LATENCY_UPPER_BOUND_MS > elapsed_time_ms, f"Error: latency {elapsed_time_ms} exceeded {EXPECTED_LATENCY_UPPER_BOUND_MS} ms"
52+
53+
resp = OrderbookActiveOrdersResponse(**response)
54+
55+
resp.validate(address)
56+
57+
2958
def run_test_portfolio_assets(environment_url):
3059
sqs_service = SERVICE_MAP[environment_url]
31-
60+
3261
# Arbitrary addresses
3362
addresses = [
3463
"osmo1044qatzg4a0wm63jchrfdnn2u8nwdgxxt6e524",
@@ -64,6 +93,7 @@ def run_test_portfolio_assets(environment_url):
6493
total_assets = categories.get(total_assets_category_name)
6594
validate_category(total_assets, True)
6695

96+
6797
def validate_category(category, should_have_breakdown=False):
6898
assert category is not None
6999

@@ -80,9 +110,7 @@ def validate_category(category, should_have_breakdown=False):
80110

81111
account_coins_result = category.get('account_coins_result')
82112
assert account_coins_result is not None
83-
113+
84114
for coin_result in account_coins_result:
85115
assert coin_result.get('coin') is not None
86116
assert coin_result.get('cap_value') is not None
87-
88-

tests/test_synthetic_geo.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from util import *
99

1010
from test_pools import run_pool_liquidity_cap_test, run_canonical_orderbook_test, run_pool_filters_test
11-
from test_passthrough import run_test_portfolio_assets
11+
from test_passthrough import run_test_portfolio_assets, run_test_active_orderbook_orders
1212
from test_candidate_routes import run_candidate_routes_test
1313
from test_router_quote_out_given_in import TestExactAmountInQuote
1414
from test_router_quote_in_given_out import TestExactAmountOutQuote
@@ -41,6 +41,10 @@ def test_synth_pools_filters(self, environment_url):
4141
def test_synth_passthrough_portfolio_assets(self, environment_url):
4242
run_test_portfolio_assets(environment_url)
4343

44+
# /passthrough/active-orders endpoint
45+
def test_synth_passthrough_active_orders(self, environment_url):
46+
run_test_active_orderbook_orders(environment_url)
47+
4448
# /router/routes endpoint
4549
def test_synth_candidate_routes(self, environment_url):
4650
tokens_to_pair = [constants.USDC, constants.UOSMO]

0 commit comments

Comments
 (0)