Skip to content

Commit 09c89e6

Browse files
committed
test(exchange-rate): Add tests which verify that rate conversions fail correctly
1 parent 754c764 commit 09c89e6

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

crates/interledger-service-util/src/exchange_rates_service.rs

+157
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,160 @@ where
141141
Box::new(self.next.send_request(request))
142142
}
143143
}
144+
145+
#[cfg(test)]
146+
mod tests {
147+
use super::*;
148+
use futures::{future::ok, Future};
149+
use interledger_ildcp::IldcpAccount;
150+
use interledger_packet::{Address, FulfillBuilder, PrepareBuilder};
151+
use interledger_service::{outgoing_service_fn, Account};
152+
use std::collections::HashMap;
153+
use std::str::FromStr;
154+
use std::{
155+
sync::{Arc, Mutex},
156+
time::SystemTime,
157+
};
158+
159+
#[test]
160+
fn exchange_rate_ok() {
161+
let ret = exchange_rate(100, 1, 1.0, 1, 2.0);
162+
assert_eq!(ret.1[0].prepare.amount(), 200);
163+
164+
let ret = exchange_rate(1_000_000, 1, 3.0, 1, 2.0);
165+
assert_eq!(ret.1[0].prepare.amount(), 666_666);
166+
}
167+
168+
#[test]
169+
fn exchange_conversion_error() {
170+
// rejects f64 that does not fit in u64
171+
let ret = exchange_rate(std::u64::MAX, 1, 1.0, 1, 2.0);
172+
let reject = ret.0.unwrap_err();
173+
assert_eq!(reject.code(), ErrorCode::F08_AMOUNT_TOO_LARGE);
174+
assert!(reject.message().starts_with(b"Could not cast"));
175+
176+
// `Convert` errored
177+
let ret = exchange_rate(std::u64::MAX, 1, 1.0, 255, std::f64::MAX);
178+
let reject = ret.0.unwrap_err();
179+
assert_eq!(reject.code(), ErrorCode::F08_AMOUNT_TOO_LARGE);
180+
assert!(reject.message().starts_with(b"Could not convert"));
181+
}
182+
183+
// Instantiates an exchange rate service and returns the fulfill/reject
184+
// packet and the outgoing request after performing an asset conversion
185+
fn exchange_rate(
186+
amount: u64,
187+
scale1: u8,
188+
rate1: f64,
189+
scale2: u8,
190+
rate2: f64,
191+
) -> (Result<Fulfill, Reject>, Vec<OutgoingRequest<TestAccount>>) {
192+
let requests = Arc::new(Mutex::new(Vec::new()));
193+
let requests_clone = requests.clone();
194+
let outgoing = outgoing_service_fn(move |request| {
195+
requests_clone.lock().unwrap().push(request);
196+
Box::new(ok(FulfillBuilder {
197+
fulfillment: &[0; 32],
198+
data: b"hello!",
199+
}
200+
.build()))
201+
});
202+
let mut service = test_service(rate1, rate2, outgoing);
203+
let result = service
204+
.send_request(OutgoingRequest {
205+
from: TestAccount::new("ABC".to_owned(), scale1),
206+
to: TestAccount::new("XYZ".to_owned(), scale2),
207+
original_amount: amount,
208+
prepare: PrepareBuilder {
209+
destination: Address::from_str("example.destination").unwrap(),
210+
amount,
211+
expires_at: SystemTime::now(),
212+
execution_condition: &[1; 32],
213+
data: b"hello",
214+
}
215+
.build(),
216+
})
217+
.wait();
218+
219+
let reqs = requests.lock().unwrap();
220+
(result, reqs.clone())
221+
}
222+
223+
#[derive(Debug, Clone)]
224+
struct TestAccount {
225+
ilp_address: Address,
226+
asset_code: String,
227+
asset_scale: u8,
228+
}
229+
impl TestAccount {
230+
fn new(asset_code: String, asset_scale: u8) -> Self {
231+
TestAccount {
232+
ilp_address: Address::from_str("example.alice").unwrap(),
233+
asset_code,
234+
asset_scale,
235+
}
236+
}
237+
}
238+
239+
impl Account for TestAccount {
240+
type AccountId = u64;
241+
242+
fn id(&self) -> u64 {
243+
0
244+
}
245+
}
246+
247+
impl IldcpAccount for TestAccount {
248+
fn asset_code(&self) -> &str {
249+
&self.asset_code
250+
}
251+
252+
fn asset_scale(&self) -> u8 {
253+
self.asset_scale
254+
}
255+
256+
fn client_address(&self) -> &Address {
257+
&self.ilp_address
258+
}
259+
}
260+
261+
#[derive(Debug, Clone)]
262+
struct TestStore {
263+
rates: HashMap<Vec<String>, (f64, f64)>,
264+
}
265+
266+
impl ExchangeRateStore for TestStore {
267+
fn get_exchange_rates(&self, asset_codes: &[&str]) -> Result<Vec<f64>, ()> {
268+
let mut ret = Vec::new();
269+
let key = vec![asset_codes[0].to_owned(), asset_codes[1].to_owned()];
270+
let v = self.rates.get(&key);
271+
if let Some(v) = v {
272+
ret.push(v.0);
273+
ret.push(v.1);
274+
} else {
275+
return Err(());
276+
}
277+
Ok(ret)
278+
}
279+
}
280+
281+
fn test_store(rate1: f64, rate2: f64) -> TestStore {
282+
let mut rates = HashMap::new();
283+
rates.insert(vec!["ABC".to_owned(), "XYZ".to_owned()], (rate1, rate2));
284+
TestStore { rates }
285+
}
286+
287+
fn test_service(
288+
rate1: f64,
289+
rate2: f64,
290+
handler: impl OutgoingService<TestAccount> + Clone + Send + Sync,
291+
) -> ExchangeRateService<
292+
TestStore,
293+
impl OutgoingService<TestAccount> + Clone + Send + Sync,
294+
TestAccount,
295+
> {
296+
let store = test_store(rate1, rate2);
297+
ExchangeRateService::new(Address::from_str("example.bob").unwrap(), store, handler)
298+
}
299+
300+
}

0 commit comments

Comments
 (0)