1+ """Tests for basic SMASH operations."""
2+
3+
4+ import logging
5+ import requests
6+ from http import HTTPStatus
7+ import pytest
8+ from cardano_clusterlib import clusterlib
9+
10+ from cardano_node_tests .utils import logfiles
11+ from cardano_node_tests .utils import configuration
12+ from cardano_node_tests .utils import dbsync_utils
13+ from cardano_node_tests .utils import dbsync_queries
14+ from cardano_node_tests .utils import smash_utils
15+ from cardano_node_tests .utils import helpers
16+
17+ LOGGER = logging .getLogger (__name__ )
18+
19+
20+ @pytest .fixture (autouse = True )
21+ def check_smash_availability ():
22+ """Fixture to check SMASH availability before each test."""
23+ if not configuration .HAS_SMASH :
24+ pytest .skip ("Skipping test because SMASH service is not available." )
25+
26+
27+ def check_request_error (err , expected_status : HTTPStatus , expected_code : str | None , expected_description : str ):
28+ """Assert expected HTTP errors in requests, handling both JSON and text responses."""
29+ response = err .response
30+ assert response .status_code == expected_status
31+
32+ try :
33+ error_data = response .json ()
34+ actual_code = error_data .get ("code" )
35+ actual_description = error_data .get ("description" )
36+ except ValueError :
37+ # If not JSON, treat the entire response as text
38+ actual_code = None
39+ actual_description = response .text .strip ()
40+
41+ if expected_code :
42+ assert actual_code == expected_code
43+
44+ assert actual_description == expected_description
45+
46+
47+ class TestBasicSmash :
48+ """Basic tests for SMASH service."""
49+
50+ @pytest .fixture ()
51+ def locked_pool (
52+ self ,
53+ cluster_lock_pool : clusterlib .ClusterLib ,
54+ ) -> dbsync_queries .PoolDataDBRow :
55+ """Get pool id from cluster with locked pool."""
56+ cluster_obj , pool_name = cluster_lock_pool
57+ pools_ids = cluster_obj .g_query .get_stake_pools ()
58+ locked_pool_number = pool_name .replace ("node-pool" , "" )
59+ pools = [next (dbsync_queries .query_pool_data (p )) for p in pools_ids ]
60+ locked_pool_data = next ((item for item in pools if 'pool' + locked_pool_number in item .metadata_url ), None )
61+ return locked_pool_data
62+
63+ @pytest .fixture (scope = "session" )
64+ def smash (
65+ self ,
66+ ) -> smash_utils .SmashClient :
67+ """Create SMASH client."""
68+ smash = smash_utils .get_client ()
69+ return smash
70+
71+ def test_fetch_pool_metadata (
72+ self ,
73+ locked_pool : dbsync_queries .PoolDataDBRow ,
74+ smash : smash_utils .SmashClient
75+ ):
76+ pool_id = locked_pool .view
77+
78+ # Offchain metadata is inserted into database few minutes after start of a cluster
79+ def _query_func ():
80+ pool_metadata = next (iter (dbsync_queries .query_off_chain_pool_data (pool_id )), None )
81+ assert pool_metadata != None , dbsync_utils .NO_RESPONSE_STR
82+ return pool_metadata
83+ metadata_dbsync = dbsync_utils .retry_query (query_func = _query_func , timeout = 360 )
84+
85+ expected_metadata = smash_utils .PoolMetadata (
86+ name = metadata_dbsync .json ["name" ],
87+ description = metadata_dbsync .json ["description" ],
88+ ticker = metadata_dbsync .ticker_name ,
89+ homepage = metadata_dbsync .json ["homepage" ]
90+ )
91+ actual_metadata = smash .get_pool_metadata (pool_id , metadata_dbsync .hash .hex ())
92+ assert expected_metadata == actual_metadata
93+
94+ def test_delist_pool (
95+ self ,
96+ locked_pool : dbsync_queries .PoolDataDBRow ,
97+ smash : smash_utils .SmashClient ,
98+ request : pytest .FixtureRequest ,
99+ worker_id : str ,
100+ ):
101+ pool_id = locked_pool .view
102+
103+ # Define and register function that ensures pool is re-enlisted after test is completion
104+ def pool_cleanup ():
105+ smash .enlist_pool (pool_id )
106+ request .addfinalizer (pool_cleanup )
107+
108+ # Delist the pool
109+ pool_data = dbsync_utils .get_pool_data (pool_id )
110+ expected_delisted_pool = smash_utils .PoolData (pool_id = pool_data .hash )
111+ actual_delisted_pool = smash .delist_pool (pool_id )
112+ assert expected_delisted_pool == actual_delisted_pool
113+
114+ # Check if fetching metadata for a delisted pool returns an error
115+ try :
116+ smash .get_pool_metadata (pool_id , pool_data .metadata_hash )
117+ except requests .exceptions .RequestException as err :
118+ assert HTTPStatus .FORBIDDEN == err .response .status_code
119+ assert f"Pool { pool_data .hash } is delisted" == err .response .text
120+
121+ # Ignore expected errors in logs that would fail test in teardown phase
122+ err_msg = 'Delisted pool already exists!'
123+ expected_err_regexes = [err_msg ]
124+ logfiles .add_ignore_rule (
125+ files_glob = "smash.stdout" ,
126+ regex = "|" .join (expected_err_regexes ),
127+ ignore_file_id = worker_id ,
128+ )
129+ # Ensure re-delisting an already delisted pool returns an error
130+ try :
131+ smash .delist_pool (pool_id )
132+ except requests .exceptions .RequestException as err :
133+ check_request_error (err , HTTPStatus .BAD_REQUEST , "DbInsertError" , err_msg )
134+
135+ def test_enlist_pool (
136+ self ,
137+ locked_pool : dbsync_queries .PoolDataDBRow ,
138+ smash : smash_utils .SmashClient ,
139+ ):
140+ pool_id = locked_pool .view
141+
142+ # Ensure enlisting an already enlisted pool returns an error
143+ try :
144+ smash .enlist_pool (pool_id )
145+ except requests .exceptions .RequestException as err :
146+ check_request_error (err , HTTPStatus .NOT_FOUND , "RecordDoesNotExist" , "The requested record does not exist." )
147+
148+ # Delist the pool
149+ smash .delist_pool (pool_id )
150+ pool_data = dbsync_utils .get_pool_data (pool_id )
151+ try :
152+ smash .get_pool_metadata (pool_id , pool_data .metadata_hash )
153+ except requests .exceptions .RequestException as err :
154+ check_request_error (err , HTTPStatus .FORBIDDEN , None , f"Pool { pool_data .hash } is delisted" )
155+
156+ # Enlist pool
157+ actual_res_enlist = smash .enlist_pool (pool_id )
158+ expected_res_enlist = smash_utils .PoolData (pool_id = pool_data .hash )
159+ assert expected_res_enlist == actual_res_enlist
160+
161+ def test_reserve_ticker (
162+ self ,
163+ locked_pool : dbsync_queries .PoolDataDBRow ,
164+ smash : smash_utils .SmashClient ,
165+ request : pytest .FixtureRequest ,
166+ ):
167+ pool_id = locked_pool .view
168+
169+ # Register cleanup function that removes ticker from database after test completion
170+ request .addfinalizer (dbsync_queries .delete_reserved_pool_tickers )
171+
172+ # Reserve ticker
173+ ticker = helpers .get_rand_str (length = 3 )
174+ actual_response = smash .reserve_ticker (ticker_name = ticker , pool_hash = pool_id )
175+ expected_response = {'name' : f"{ ticker } " }
176+ assert expected_response == actual_response
177+
178+ # Reserve already taken ticker
179+ try :
180+ smash .reserve_ticker (ticker_name = ticker , pool_hash = pool_id )
181+ except requests .exceptions .RequestException as err :
182+ check_request_error (
183+ err , HTTPStatus .BAD_REQUEST , "TickerAlreadyReserved" ,
184+ f"Ticker name \" { ticker } \" is already reserved"
185+ )
186+
187+
0 commit comments