11use std:: collections:: BTreeMap ;
2+ use std:: time:: Duration ;
23
34use apollo_config:: converters:: UrlAndHeaders ;
45use apollo_l1_gas_price_types:: errors:: EthToStrkOracleClientError ;
@@ -20,19 +21,21 @@ async fn make_server(server: &mut ServerGuard, body: serde_json::Value) -> Mock
2021
2122#[ tokio:: test]
2223async fn eth_to_fri_rate_uses_cache_on_quantized_hit ( ) {
23- let expected_rate = 123456 ;
24- let expected_rate_hex = format ! ( "0x{expected_rate:x}" ) ;
25- let timestamp1 = 1234567890 ;
26- let timestamp2 = timestamp1 + 10 ; // Still in the same quantized bucket
27- let lag_interval_seconds = 60 ;
24+ const EXPECTED_RATE : u128 = 123456 ;
25+ let expected_rate_hex = format ! ( "0x{EXPECTED_RATE:x}" ) ;
26+ const TIMESTAMP1 : u64 = 1234567890 ;
27+ const TIMESTAMP_OFFSET : u64 = 10 ;
28+ // Still in the same quantized bucket.
29+ const TIMESTAMP2 : u64 = TIMESTAMP1 + TIMESTAMP_OFFSET ;
30+ const LAG_INTERVAL_SECONDS : u64 = 60 ;
2831
29- let quantized_timestamp = ( timestamp1 - lag_interval_seconds ) / lag_interval_seconds ;
30- let adjusted_timestamp = quantized_timestamp * lag_interval_seconds ;
32+ let quantized_timestamp = ( TIMESTAMP1 - LAG_INTERVAL_SECONDS ) / LAG_INTERVAL_SECONDS ;
33+ let adjusted_timestamp = quantized_timestamp * LAG_INTERVAL_SECONDS ;
3134
3235 let mut server = mockito:: Server :: new_async ( ) . await ;
3336
3437 // Define a mock response for a GET request with a specific adjusted_timestamp in the path
35- let _m = server
38+ let _mock_response = server
3639 . mock ( "GET" , "/" ) // Match the base path only.
3740 . match_query ( mockito:: Matcher :: UrlEncoded ( "timestamp" . into ( ) , adjusted_timestamp. to_string ( ) ) )
3841 . with_header ( "Content-Type" , "application/json" )
@@ -49,38 +52,128 @@ async fn eth_to_fri_rate_uses_cache_on_quantized_hit() {
4952 headers : BTreeMap :: new ( ) , // No additional headers needed for this test.
5053 } ;
5154 let url_header_list = Some ( vec ! [ url_and_headers] ) ;
52- let config =
53- EthToStrkOracleConfig { url_header_list, lag_interval_seconds, ..Default :: default ( ) } ;
55+ let config = EthToStrkOracleConfig {
56+ url_header_list,
57+ lag_interval_seconds : LAG_INTERVAL_SECONDS ,
58+ ..Default :: default ( )
59+ } ;
5460 let client = EthToStrkOracleClient :: new ( config. clone ( ) ) ;
5561
5662 // First request should fail because the cache is empty.
57- assert ! ( client. eth_to_fri_rate( timestamp1 ) . await . is_err( ) ) ;
63+ assert ! ( client. eth_to_fri_rate( TIMESTAMP1 ) . await . is_err( ) ) ;
5864 // Wait for the query to resolve.
59- while client. eth_to_fri_rate ( timestamp1 ) . await . is_err ( ) {
65+ while client. eth_to_fri_rate ( TIMESTAMP1 ) . await . is_err ( ) {
6066 tokio:: task:: yield_now ( ) . await ; // Don't block the executor.
6167 }
62- let rate1 = client. eth_to_fri_rate ( timestamp1 ) . await . unwrap ( ) ;
68+ let rate1 = client. eth_to_fri_rate ( TIMESTAMP1 ) . await . unwrap ( ) ;
6369 let rate2 = client
64- . eth_to_fri_rate ( timestamp2 )
70+ . eth_to_fri_rate ( TIMESTAMP2 )
6571 . await
6672 . expect ( "Should resolve immediately due to the cache" ) ;
6773 assert_eq ! ( rate1, rate2) ;
6874}
6975
76+ #[ tokio:: test]
77+ async fn eth_to_fri_rate_uses_prev_cache_when_query_not_ready ( ) {
78+ const EXPECTED_RATE : u128 = 123456 ;
79+ let expected_rate_hex = format ! ( "0x{EXPECTED_RATE:x}" ) ;
80+ let different_rate = EXPECTED_RATE * 2 ;
81+ let different_rate_hex = format ! ( "0x{:x}" , different_rate) ;
82+ const LAG_INTERVAL_SECONDS : u64 = 60 ;
83+
84+ const TIMESTAMP1 : u64 = 1234567890 ;
85+ const TIMESTAMP2 : u64 = TIMESTAMP1 + LAG_INTERVAL_SECONDS ;
86+
87+ let quantized_timestamp1 = ( TIMESTAMP1 - LAG_INTERVAL_SECONDS ) / LAG_INTERVAL_SECONDS ;
88+ let adjusted_timestamp1 = quantized_timestamp1 * LAG_INTERVAL_SECONDS ;
89+ let quantized_timestamp2 = ( TIMESTAMP2 - LAG_INTERVAL_SECONDS ) / LAG_INTERVAL_SECONDS ;
90+ let adjusted_timestamp2 = quantized_timestamp2 * LAG_INTERVAL_SECONDS ;
91+
92+ let mut server = mockito:: Server :: new_async ( ) . await ;
93+
94+ // Define a mock response for a GET request with a specific adjusted_timestamp in the path
95+ let _mock_response1 = server
96+ . mock ( "GET" , "/" ) // Match the base path only.
97+ . match_query ( mockito:: Matcher :: UrlEncoded ( "timestamp" . into ( ) , adjusted_timestamp1. to_string ( ) ) )
98+ . with_header ( "Content-Type" , "application/json" )
99+ . with_body (
100+ json ! ( {
101+ "price" : expected_rate_hex,
102+ "decimals" : 18
103+ } )
104+ . to_string ( ) ,
105+ )
106+ . create ( ) ;
107+ // Second response (same matcher) returns a different value on the next call.
108+ let _mock_response2 = server
109+ . mock ( "GET" , "/" )
110+ . match_query ( mockito:: Matcher :: UrlEncoded (
111+ "timestamp" . into ( ) ,
112+ adjusted_timestamp2. to_string ( ) ,
113+ ) )
114+ . with_header ( "Content-Type" , "application/json" )
115+ . with_body (
116+ json ! ( {
117+ "price" : different_rate_hex,
118+ "decimals" : 18
119+ } )
120+ . to_string ( ) ,
121+ )
122+ . create ( ) ;
123+
124+ let url_and_headers = UrlAndHeaders {
125+ url : Url :: parse ( & server. url ( ) ) . unwrap ( ) ,
126+ headers : BTreeMap :: new ( ) , // No additional headers needed for this test.
127+ } ;
128+ let url_header_list = Some ( vec ! [ url_and_headers] ) ;
129+ let config = EthToStrkOracleConfig {
130+ url_header_list,
131+ lag_interval_seconds : LAG_INTERVAL_SECONDS ,
132+ ..Default :: default ( )
133+ } ;
134+ let client = EthToStrkOracleClient :: new ( config. clone ( ) ) ;
135+
136+ // First request should fail because the cache is empty.
137+ assert ! ( client. eth_to_fri_rate( TIMESTAMP1 ) . await . is_err( ) ) ;
138+ // Wait for the query to resolve.
139+ while client. eth_to_fri_rate ( TIMESTAMP1 ) . await . is_err ( ) {
140+ tokio:: task:: yield_now ( ) . await ; // Don't block the executor.
141+ }
142+ let rate1 = client. eth_to_fri_rate ( TIMESTAMP1 ) . await . unwrap ( ) ;
143+ assert_eq ! ( rate1, EXPECTED_RATE ) ;
144+ // Second request should resolve immediately due to the cache.
145+ let rate2 = client. eth_to_fri_rate ( TIMESTAMP2 ) . await . unwrap ( ) ;
146+ assert_eq ! ( rate2, EXPECTED_RATE ) ;
147+
148+ // Wait for the query to resolve, and the price to be updated.
149+ for _ in 0 ..100 {
150+ let current_rate = client. eth_to_fri_rate ( TIMESTAMP2 ) . await . unwrap ( ) ;
151+ if current_rate > EXPECTED_RATE {
152+ break ;
153+ }
154+ tokio:: time:: sleep ( Duration :: from_millis ( 1 ) ) . await ;
155+ }
156+
157+ // Third request should already successfully get the query from the server.
158+ let rate3 = client. eth_to_fri_rate ( TIMESTAMP2 ) . await . unwrap ( ) ;
159+ assert_eq ! ( rate3, different_rate) ;
160+ }
161+
70162#[ tokio:: test]
71163async fn eth_to_fri_rate_two_urls ( ) {
72- let expected_rate = 123456 ;
73- let expected_rate_hex = format ! ( "0x{expected_rate :x}" ) ;
74- let lag_interval_seconds = 60 ;
75- let timestamp1 = 1234567890 ;
76- let timestamp2 = timestamp1 + lag_interval_seconds * 2 ; // New quantized bucket
164+ const EXPECTED_RATE : u128 = 123456 ;
165+ let expected_rate_hex = format ! ( "0x{EXPECTED_RATE :x}" ) ;
166+ const LAG_INTERVAL_SECONDS : u64 = 60 ;
167+ const TIMESTAMP1 : u64 = 1234567890 ;
168+ const TIMESTAMP2 : u64 = TIMESTAMP1 + LAG_INTERVAL_SECONDS * 2 ; // New quantized bucket
77169 let mut server1 = mockito:: Server :: new_async ( ) . await ;
78170 let mut server2 = mockito:: Server :: new_async ( ) . await ;
79171
80172 // Define a mock response with badly formatted JSON for server1
81- let _m1 = make_server ( & mut server1, json ! ( { "foo" : "0x0" , "bar" : 18 } ) ) . await ;
173+ let _mock_response1 = make_server ( & mut server1, json ! ( { "foo" : "0x0" , "bar" : 18 } ) ) . await ;
82174 // For server2 we get the expected response.
83- let _m2 = make_server ( & mut server2, json ! ( { "price" : & expected_rate_hex, "decimals" : 18 } ) ) . await ;
175+ let _mock_response2 =
176+ make_server ( & mut server2, json ! ( { "price" : & expected_rate_hex, "decimals" : 18 } ) ) . await ;
84177
85178 let url_header_list = Some ( vec ! [
86179 UrlAndHeaders {
@@ -92,25 +185,29 @@ async fn eth_to_fri_rate_two_urls() {
92185 headers: BTreeMap :: new( ) , // No additional headers needed for this test.
93186 } ,
94187 ] ) ;
95- let config =
96- EthToStrkOracleConfig { url_header_list, lag_interval_seconds, ..Default :: default ( ) } ;
188+ let config = EthToStrkOracleConfig {
189+ url_header_list,
190+ lag_interval_seconds : LAG_INTERVAL_SECONDS ,
191+ ..Default :: default ( )
192+ } ;
97193 let client = EthToStrkOracleClient :: new ( config. clone ( ) ) ;
98194 // First request should fail because the cache is empty.
99- assert ! ( client. eth_to_fri_rate( timestamp1 ) . await . is_err( ) ) ;
195+ assert ! ( client. eth_to_fri_rate( TIMESTAMP1 ) . await . is_err( ) ) ;
100196 // Wait for the query to resolve.
101- while client. eth_to_fri_rate ( timestamp1 ) . await . is_err ( ) {
197+ while client. eth_to_fri_rate ( TIMESTAMP1 ) . await . is_err ( ) {
102198 tokio:: task:: yield_now ( ) . await ; // Don't block the executor.
103199 }
104- let rate1 = client. eth_to_fri_rate ( timestamp1 ) . await . unwrap ( ) ;
105- assert_eq ! ( rate1, expected_rate ) ;
200+ let rate1 = client. eth_to_fri_rate ( TIMESTAMP1 ) . await . unwrap ( ) ;
201+ assert_eq ! ( rate1, EXPECTED_RATE ) ;
106202
107203 // Note this server fails on missing "decimals", not "price".
108- let _m3 = make_server ( & mut server2, json ! ( { "price" : & expected_rate_hex, "bar" : 18 } ) ) . await ;
204+ let _mock_response3 =
205+ make_server ( & mut server2, json ! ( { "price" : & expected_rate_hex, "bar" : 18 } ) ) . await ;
109206 // First request should fail because the cache is empty.
110- assert ! ( client. eth_to_fri_rate( timestamp2 ) . await . is_err( ) ) ;
207+ assert ! ( client. eth_to_fri_rate( TIMESTAMP2 ) . await . is_err( ) ) ;
111208 // Wait for the query to resolve.
112209 loop {
113- match client. eth_to_fri_rate ( timestamp2 ) . await {
210+ match client. eth_to_fri_rate ( TIMESTAMP2 ) . await {
114211 Ok ( _) => panic ! ( "Both servers should be returning bad JSON!" ) ,
115212 Err ( EthToStrkOracleClientError :: QueryNotReadyError ( _) ) => { }
116213 Err ( EthToStrkOracleClientError :: AllUrlsFailedError ( _, index) ) => {
0 commit comments