#[cfg(test)]
mod tests {
extern crate std;
use sep_40_oracle::PriceData;
use soroban_sdk::{Env, Vec};
use test_case::test_case;
use crate::{order_prices_by_timestamp, twap, x_price, x_prices, x_twap};
fn build_prices(env: &Env, items: &[(i128, u64)]) -> Vec<PriceData> {
let mut v = Vec::new(env);
for (price, timestamp) in items {
v.push_back(PriceData {
price: *price as i128,
timestamp: *timestamp,
});
}
v
}
fn extract_ts(v: &Vec<PriceData>) -> std::vec::Vec<u64> {
v.iter().map(|p| p.timestamp).collect()
}
#[test_case(&[] as &[(i128, u64)], &[] as &[u64]; "empty")]
#[test_case(&[(100, 1000)], &[1000]; "single element")]
#[test_case(&[(100, 1000), (200, 2000), (300, 3000)], &[1000, 2000, 3000]; "already sorted")]
#[test_case(&[(300, 3000), (200, 2000), (100, 1000)], &[1000, 2000, 3000]; "reverse order")]
#[test_case(&[(200, 2000), (100, 1000), (400, 4000), (300, 3000)], &[1000, 2000, 3000, 4000]; "random order")]
#[test_case(&[(100, 2000), (200, 2000), (300, 1000)], &[1000, 2000, 2000]; "duplicate timestamps")]
fn test_order_prices(input: &[(i128, u64)], expected_ts: &[u64]) {
let env = Env::default();
let prices = build_prices(&env, input);
let result = order_prices_by_timestamp(&prices);
assert_eq!(result.len(), expected_ts.len() as u32);
assert_eq!(extract_ts(&result).as_slice(), expected_ts);
}
#[test_case(&[] as &[(i128, u64)], None; "empty vector")]
#[test_case(&[(100, 1000)], Some(100); "single price")]
#[test_case(
&[(100, 1000), (200, 2000), (300, 3000)],
Some(200);
"multiple prices"
)]
#[test_case(
&[(0, 1000), (200, 2000)],
Some(200);
"with zero price"
)]
#[test_case(
&[(-100, 1000), (200, 2000)],
Some(200);
"with negative price"
)]
#[test_case(
&[(100, 0), (200, 2000)],
Some(200);
"with invalid timestamp"
)]
#[test_case(
&[(0, 1000), (-100, 0)],
None;
"all invalid prices -> None"
)]
fn test_twap_cases(input: &[(i128, u64)], expected: Option<i128>) {
let env = Env::default();
let prices = build_prices(&env, input);
let result = twap(&prices);
assert_eq!(result, expected);
}
#[test_case(100, 50, 0, Some(2); "simple division decimals=0 (100/50=2)")]
#[test_case(9, 2, 0, Some(4); "floor division 9/2=4")]
#[test_case(100, -50, 0, None; "negative second price -> None")]
#[test_case(-100, 50, 0, None; "negative first price -> None")]
#[test_case(0, 50, 0, None; "zero first price -> None")]
fn test_x_price_cases(a: i128, b: i128, decimals: u32, expected: Option<i128>) {
let result = x_price(
&PriceData {
price: a,
timestamp: 1,
},
&PriceData {
price: b,
timestamp: 1,
},
decimals,
);
assert_eq!(result, expected);
}
#[test_case(
&[(100, 1_000)],
&[(50, 1_000)],
0,
Some(&[(2, 1_000)][..]); "aligned single element"
)]
#[test_case(
&[(100, 1_000), (200, 2_000), (300, 3_000)],
&[(50, 1_000), (50, 2_000), (50, 3_000)],
0,
Some(&[(2, 1_000), (4, 2_000), (6, 3_000)][..]);
"aligned multiple elements"
)]
#[test_case(
&[(100, 1_000), (200, 2_000)],
&[(10, 1_500)],
0,
Some(&[(10, 1_500), (20, 2_000)][..]);
"different lengths with nearest timestamp matching"
)]
#[test_case(
&[(0, 1_000), (-100, 2_000)],
&[(50, 1_000)],
0,
Some(&[][..]);
"all invalid prices -> empty result vec"
)]
fn test_x_prices_cases(
prices_a: &[(i128, u64)],
prices_b: &[(i128, u64)],
decimals: u32,
expected: Option<&[(i128, u64)]>,
) {
let env = Env::default();
let a = build_prices(&env, prices_a);
let b = build_prices(&env, prices_b);
let result = x_prices(&a, &b, decimals);
let mapped_result = result.map(|v| {
v.iter()
.map(|p| (p.price, p.timestamp))
.collect::<std::vec::Vec<_>>()
});
let expected_vec = expected.map(|s| s.to_vec());
assert_eq!(mapped_result, expected_vec);
}
#[test_case(
&[] as &[(i128, u64)],
&[(100, 1_000)],
0,
None;
"empty prices_a -> None"
)]
#[test_case(
&[(100, 1_000)],
&[] as &[(i128, u64)],
0,
None;
"empty prices_b -> None"
)]
#[test_case(
&[(2000000000, 1_000)],
&[(1000000000, 1_000)],
7,
Some(20000000);
"single aligned price -> TWAP is that cross price"
)]
#[test_case(
&[(0, 1_000)],
&[(100, 1_000)],
7,
None;
"invalid cross price -> None"
)]
fn test_x_twap_cases(
prices_a: &[(i128, u64)],
prices_b: &[(i128, u64)],
decimals: u32,
expected: Option<i128>,
) {
let env = Env::default();
let a = build_prices(&env, prices_a);
let b = build_prices(&env, prices_b);
let result = x_twap(&a, &b, decimals);
assert_eq!(result, expected);
}
}