Merge branch 'tokensPage' into 'master'
Tokens page See merge request riftenlabs/riftenlabs-indexer!15
This commit is contained in:
commit
8fc7fc74bb
3 changed files with 562 additions and 0 deletions
|
|
@ -51,6 +51,10 @@ pub fn prepare_tables(conn: &Connection) {
|
|||
)
|
||||
.unwrap();
|
||||
conn.execute("CREATE INDEX idx_utxo_funding_tvl_highest ON utxo_funding(token_id, new_utxo_hash, sats, token_amount);", []).unwrap();
|
||||
// Create the new index for (pool, COALESCE(first_seen_timestamp, mtp_timestamp))
|
||||
conn.execute("CREATE INDEX idx_pool_history_entry_pool_timestamp ON pool_history_entry (pool, COALESCE(first_seen_timestamp, mtp_timestamp));",
|
||||
[],
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
pub fn delete_entries_for_block(tx: &Connection, blockhash: &BlockHash) -> Result<bool> {
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ fn launch() -> _ {
|
|||
rpc::tokens::list_by_volume,
|
||||
rpc::price::price_history,
|
||||
rpc::price::price_current,
|
||||
rpc::price::price_at,
|
||||
rpc::pool::list_pools_by_apy,
|
||||
rpc::pool::list_active_pools,
|
||||
rpc::contract::contract_count_token,
|
||||
|
|
|
|||
557
src/rpc/price.rs
557
src/rpc/price.rs
|
|
@ -4,6 +4,8 @@
|
|||
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bitcoin_hashes::hex::{FromHex, ToHex};
|
||||
use bitcoincash::TokenID;
|
||||
use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State};
|
||||
use rusqlite::{params, Connection};
|
||||
use rust_decimal::prelude::*;
|
||||
|
|
@ -182,6 +184,117 @@ fn historic_price(
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn price_at_or_before(
|
||||
connection: &Connection,
|
||||
timestamp: i64,
|
||||
token_id: &TokenID,
|
||||
) -> Result<(i64, f64)> {
|
||||
let sql = "
|
||||
WITH max_timestamps AS (
|
||||
SELECT
|
||||
phe.pool,
|
||||
MAX(COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp)) AS max_effective_timestamp
|
||||
FROM
|
||||
pool_history_entry phe
|
||||
JOIN
|
||||
utxo_funding uf ON phe.utxo = uf.new_utxo_hash
|
||||
WHERE
|
||||
COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) <= ?
|
||||
AND uf.token_id = ?
|
||||
GROUP BY
|
||||
phe.pool
|
||||
)
|
||||
SELECT
|
||||
uf.token_amount,
|
||||
uf.sats,
|
||||
COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) AS effective_timestamp
|
||||
FROM
|
||||
pool_history_entry phe
|
||||
JOIN
|
||||
utxo_funding uf ON phe.utxo = uf.new_utxo_hash
|
||||
JOIN
|
||||
pool p ON p.creation_utxo = phe.pool
|
||||
JOIN
|
||||
max_timestamps mt ON phe.pool = mt.pool
|
||||
AND COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) = mt.max_effective_timestamp
|
||||
WHERE
|
||||
p.withdrawn_in_utxo IS NULL
|
||||
";
|
||||
|
||||
let mut statement = connection.prepare(sql)?;
|
||||
let mut rows = statement.query(params![timestamp, token_id.to_hex()])?;
|
||||
|
||||
let mut sum_sats: u64 = 0;
|
||||
let mut sum_tokens: u64 = 0;
|
||||
let mut latest_timestamp = 0;
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
let token_amount: i64 = row.get(0)?;
|
||||
let sats: i64 = row.get(1)?;
|
||||
let row_timestamp: i64 = row.get(2)?;
|
||||
|
||||
if sats >= 0 && token_amount >= 0 {
|
||||
sum_sats += sats as u64;
|
||||
sum_tokens += token_amount as u64;
|
||||
}
|
||||
|
||||
if row_timestamp > latest_timestamp {
|
||||
latest_timestamp = row_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
let sum_sats_decimal = Decimal::from_u64(sum_sats).unwrap_or_default();
|
||||
let sum_tokens_decimal = Decimal::from_u64(sum_tokens).unwrap_or_default();
|
||||
|
||||
let overall_price = sum_sats_decimal
|
||||
.checked_div(sum_tokens_decimal)
|
||||
.context("Division failed: sum_tokens is zero or invalid")?;
|
||||
|
||||
let overall_price_f64 = overall_price.to_f64().context("Conversion to f64 failed")?;
|
||||
|
||||
Ok((latest_timestamp, overall_price_f64))
|
||||
}
|
||||
|
||||
#[get("/price/<token>/at/<timestamp>")]
|
||||
pub fn price_at(
|
||||
token: &str,
|
||||
timestamp: &str,
|
||||
conn: &State<DB>,
|
||||
) -> Result<Json<Value>, Custom<String>> {
|
||||
let timestamp: i64 = timestamp.parse().map_err(|_| {
|
||||
Custom(
|
||||
Status::BadRequest,
|
||||
"Invalid timestamp format. Must be a valid number.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let db = conn
|
||||
.cauldron_r
|
||||
.get()
|
||||
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
|
||||
|
||||
let current_time = time_now();
|
||||
if timestamp > current_time {
|
||||
return Err(Custom(
|
||||
Status::BadRequest,
|
||||
"Timestamp is in the future".to_string(),
|
||||
));
|
||||
}
|
||||
let token = TokenID::from_hex(token)
|
||||
.map_err(|e| Custom(Status::BadRequest, format!("Error: {}", e)))?;
|
||||
|
||||
match price_at_or_before(&db, timestamp, &token) {
|
||||
Ok((latest_timestamp, price)) => Ok(Json(json!({
|
||||
"timestamp": latest_timestamp,
|
||||
"price": price
|
||||
}))),
|
||||
Err(e) => Err(Custom(
|
||||
Status::InternalServerError,
|
||||
format!("Error fetching price: {}", e),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/price/<token>/current")]
|
||||
pub fn price_current(token: &str, conn: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -237,3 +350,447 @@ pub fn price_history(
|
|||
"history": json!(history_json)
|
||||
})))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::cauldron::{pool, tx, utxo_funding};
|
||||
|
||||
use super::*;
|
||||
use bitcoin_hashes::Hash;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::routes;
|
||||
use rusqlite::{params, Connection};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn setup_mock_db(connection: &Connection) {
|
||||
utxo_funding::create_table(connection);
|
||||
tx::create_table(connection);
|
||||
pool::create_table(connection);
|
||||
|
||||
// Insert mock data into utxo_funding
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["mock_utxo_hash1", "test_txid1", 50000, 1000, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert test data 1 into utxo_funding");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["mock_utxo_hash2", "test_txid2", 60000, 2000, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert test data 2 into utxo_funding");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["mock_utxo_hash3", "test_txid3", 90000, 3000, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert test data 3 into utxo_funding");
|
||||
|
||||
// Insert mock data into tx table
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO tx (txid, first_seen_timestamp, mtp_timestamp) VALUES (?, ?, ?)",
|
||||
params!["test_txid1", 1727963300, 1727963300],
|
||||
)
|
||||
.expect("Failed to insert test data 1 into tx");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO tx (txid, first_seen_timestamp, mtp_timestamp) VALUES (?, ?, ?)",
|
||||
params!["test_txid2", 1727963350, 1727963350],
|
||||
)
|
||||
.expect("Failed to insert test data 2 into tx");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO tx (txid, first_seen_timestamp, mtp_timestamp) VALUES (?, ?, ?)",
|
||||
params!["test_txid3", 1727963400, 1727963400],
|
||||
)
|
||||
.expect("Failed to insert test data 3 into tx");
|
||||
|
||||
// Insert mock data into `pool_history_entry`
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["pool1", "mock_utxo_hash1", "test_txid1", "tx_pos1", 1727963300, 1727963300],
|
||||
)
|
||||
.expect("Failed to insert test data 1 into pool_history_entry");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["pool2", "mock_utxo_hash2", "test_txid2", "tx_pos2", 1727963350, 1727963350],
|
||||
)
|
||||
.expect("Failed to insert test data 2 into pool_history_entry");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["pool3", "mock_utxo_hash3", "test_txid3", "tx_pos3", 1727963400, 1727963400],
|
||||
)
|
||||
.expect("Failed to insert test data 3 into pool_history_entry");
|
||||
|
||||
// Insert newer entry for pool1
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["pool1", "mock_utxo_hash1_newer", "test_txid1_newer", "tx_pos1_newer", 1727963500, 1727963500],
|
||||
)
|
||||
.expect("Failed to insert newer test data into pool_history_entry");
|
||||
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["mock_utxo_hash1_newer", "test_txid1_newer", 70000, 1500, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert newer test data into utxo_funding");
|
||||
|
||||
// Insert active pools with required columns (owner_pkh and token_id)
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool (creation_utxo, owner_pkh, token_id, withdrawn_in_utxo) VALUES (?, ?, ?, ?)",
|
||||
params!["pool1", "dummy_owner_pkh1", "dummy_token_id1", Option::<String>::None], // Active pool
|
||||
)
|
||||
.expect("Failed to insert test data 1 into pool");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool (creation_utxo, owner_pkh, token_id, withdrawn_in_utxo) VALUES (?, ?, ?, ?)",
|
||||
params!["pool2", "dummy_owner_pkh2", "dummy_token_id2", Option::<String>::None], // Active pool
|
||||
)
|
||||
.expect("Failed to insert test data 2 into pool");
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool (creation_utxo, owner_pkh, token_id, withdrawn_in_utxo) VALUES (?, ?, ?, ?)",
|
||||
params!["pool3", "dummy_owner_pkh3", "dummy_token_id3", Option::<String>::None], // Active pool
|
||||
)
|
||||
.expect("Failed to insert test data 3 into pool");
|
||||
|
||||
// Insert an inactive pool
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool (creation_utxo, owner_pkh, token_id, withdrawn_in_utxo) VALUES (?, ?, ?, ?)",
|
||||
params!["inactive_pool", "dummy_owner_pkh_inactive", "dummy_token_id_inactive", "inactive_utxo"], // Inactive pool
|
||||
)
|
||||
.expect("Failed to insert inactive pool");
|
||||
|
||||
// Insert corresponding utxo_funding for the inactive pool
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["inactive_utxo", "inactive_txid", 80000, 2500, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert inactive utxo_funding");
|
||||
|
||||
// Insert pool_history_entry for the inactive pool
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["inactive_pool", "inactive_utxo", "inactive_txid", "tx_pos_inactive", 1727963350, 1727963350],
|
||||
)
|
||||
.expect("Failed to insert inactive pool_history_entry");
|
||||
}
|
||||
|
||||
// Mock function to create a DB pool
|
||||
fn mock_db_pool() -> DB {
|
||||
let manager = SqliteConnectionManager::memory();
|
||||
let pool = Pool::new(manager).expect("Failed to create pool.");
|
||||
|
||||
let write_conn = pool.get().expect("Failed to get connection.");
|
||||
setup_mock_db(&write_conn);
|
||||
|
||||
DB {
|
||||
cauldron_w: Arc::new(pool.clone()),
|
||||
cauldron_r: Arc::new(pool.clone()),
|
||||
bcmr_w: Arc::new(pool.clone()),
|
||||
bcmr_r: Arc::new(pool.clone()),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_price_at_specific_time() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db)
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
let token_id = TokenID::all_zeros().to_hex();
|
||||
|
||||
// Test 1: Querying at a timestamp that exactly matches test_txid1
|
||||
let timestamp = 1727963300;
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let json_value: serde_json::Value =
|
||||
serde_json::from_str(response.into_string().unwrap().as_str()).unwrap();
|
||||
let actual_price = json_value["price"]
|
||||
.as_f64()
|
||||
.expect("Price field is not a valid f64");
|
||||
|
||||
// Expected price based on 50,000 sats and 1,000 tokens
|
||||
let expected_price = 50.0;
|
||||
assert!((actual_price - expected_price).abs() < 0.01);
|
||||
|
||||
// Test 2: Querying at a timestamp that includes Pool 1 and Pool 2
|
||||
let timestamp = 1727963350;
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json_value: serde_json::Value =
|
||||
serde_json::from_str(response.into_string().unwrap().as_str()).unwrap();
|
||||
let actual_price = json_value["price"]
|
||||
.as_f64()
|
||||
.expect("Price field is not a valid f64");
|
||||
|
||||
// The combined price should be 36.67 (rounded)
|
||||
let expected_price = 36.67;
|
||||
assert!((actual_price - expected_price).abs() < 0.01);
|
||||
|
||||
// Test 3: Querying a timestamp that includes Pool 1, Pool 2, and Pool 3
|
||||
let timestamp = 1727963400;
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json_value: serde_json::Value =
|
||||
serde_json::from_str(response.into_string().unwrap().as_str()).unwrap();
|
||||
let actual_price = json_value["price"]
|
||||
.as_f64()
|
||||
.expect("Price field is not a valid f64");
|
||||
|
||||
// The combined price should be 33.33 (rounded)
|
||||
let expected_price = 33.33;
|
||||
assert!((actual_price - expected_price).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_price_at_newer_timestamp_for_pool1() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db.clone())
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
let token_id = TokenID::all_zeros().to_hex();
|
||||
|
||||
// Test: Query at a newer timestamp (1727963500) for pool1 and ensure it only accounts for the newer entry
|
||||
let timestamp = 1727963500; // Newer timestamp
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let json_value: serde_json::Value =
|
||||
serde_json::from_str(response.into_string().unwrap().as_str()).unwrap();
|
||||
let actual_price = json_value["price"]
|
||||
.as_f64()
|
||||
.expect("Price field is not a valid f64");
|
||||
|
||||
// Expected price based on the total from pool1, pool2, and pool3
|
||||
let expected_price = 33.85; // Rounded to 2 decimal places
|
||||
assert!((actual_price - expected_price).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_price_with_multiple_entries_at_same_timestamp() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db.clone())
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
let token_id = TokenID::all_zeros().to_hex();
|
||||
|
||||
// Insert additional entries with the same timestamp for an existing pool (e.g., pool1)
|
||||
let connection = mock_db.cauldron_r.get().expect("Failed to get connection.");
|
||||
|
||||
// These entries should have the same timestamp as previous mockdata and be counted in the price calculation
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params!["pool1", "mock_utxo_hash1_extra", "test_txid1_extra", "0", 1727963300, 1727963300],
|
||||
)
|
||||
.expect("Failed to insert extra test data into pool_history_entry");
|
||||
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params!["mock_utxo_hash1_extra", "test_txid1_extra", 40000, 500, "0000000000000000000000000000000000000000000000000000000000000000"],
|
||||
)
|
||||
.expect("Failed to insert extra test data into utxo_funding");
|
||||
|
||||
// Test: Query at the timestamp matching test_txid1 (1727963300) and check the price
|
||||
let timestamp = 1727963300;
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let json_value: serde_json::Value =
|
||||
serde_json::from_str(response.into_string().unwrap().as_str()).unwrap();
|
||||
let actual_price = json_value["price"]
|
||||
.as_f64()
|
||||
.expect("Price field is not a valid f64");
|
||||
|
||||
// Expected price based on (50,000 + 40,000) sats and (1,000 + 500) tokens = 90,000 / 1,500 = 60.0
|
||||
let expected_price = 60.0;
|
||||
assert!((actual_price - expected_price).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_token_hash() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db)
|
||||
.mount("/cauldron", routes![price_current, price_history, price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
// Test the `/price/<token>/at/<timestamp>` endpoint with a bad token hash
|
||||
let bad_token_id = "bad_token_id";
|
||||
let timestamp = 1727963432; // Arbitrary timestamp
|
||||
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", bad_token_id, timestamp))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::BadRequest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_in_future() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db)
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
let token_id = TokenID::all_zeros().to_hex();
|
||||
let future_timestamp = (time_now() + 100000).to_string();
|
||||
|
||||
let response = client
|
||||
.get(format!(
|
||||
"/cauldron/price/{}/at/{}",
|
||||
token_id, future_timestamp
|
||||
))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::BadRequest);
|
||||
let body = response.into_string().unwrap();
|
||||
assert!(body.contains("Timestamp is in the future"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_timestamp_format() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db)
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
// Test the `/price/<token>/at/<timestamp>` endpoint with an invalid timestamp
|
||||
let token_id = TokenID::all_zeros().to_hex();
|
||||
let invalid_timestamp = "ASDASD:";
|
||||
|
||||
let response = client
|
||||
.get(format!(
|
||||
"/cauldron/price/{}/at/{}",
|
||||
token_id, invalid_timestamp
|
||||
))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::BadRequest);
|
||||
let body = response.into_string().unwrap();
|
||||
assert!(body.contains("Invalid timestamp format"));
|
||||
}
|
||||
#[test]
|
||||
fn test_price_with_high_tokens_and_low_sats() {
|
||||
let mock_db = mock_db_pool();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(mock_db.clone())
|
||||
.mount("/cauldron", routes![price_at]);
|
||||
|
||||
let client = Client::tracked(rocket).expect("valid rocket instance");
|
||||
|
||||
let connection = mock_db.cauldron_r.get().expect("Failed to get connection.");
|
||||
|
||||
// Setup mock data for this specific test
|
||||
connection
|
||||
.execute(
|
||||
"CREATE TABLE IF NOT EXISTS utxo_funding (
|
||||
new_utxo_hash TEXT PRIMARY KEY,
|
||||
txid TEXT,
|
||||
sats BIGINT,
|
||||
token_amount BIGINT,
|
||||
token_id TEXT
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.expect("Failed to create utxo_funding table");
|
||||
|
||||
connection
|
||||
.execute(
|
||||
"CREATE TABLE IF NOT EXISTS pool_history_entry (
|
||||
pool TEXT,
|
||||
utxo TEXT PRIMARY KEY,
|
||||
txid TEXT,
|
||||
tx_pos TEXT,
|
||||
mtp_timestamp BIGINT,
|
||||
first_seen_timestamp BIGINT
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.expect("Failed to create pool_history_entry table");
|
||||
|
||||
// Insert high tokens and low sats
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params![
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
1_i64, // Low sats
|
||||
9999999999999999_i64, // Very high token amount
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
],
|
||||
)
|
||||
.expect("Failed to insert high-token data into utxo_funding");
|
||||
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params![
|
||||
"pool_high_tokens",
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
"tx_pos_high_tokens",
|
||||
1727963300,
|
||||
1727963300
|
||||
],
|
||||
)
|
||||
.expect("Failed to insert high-token data into pool_history_entry");
|
||||
|
||||
// Test: Querying the price with high tokens and low sats
|
||||
let token_id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let timestamp = 1727963300;
|
||||
let response = client
|
||||
.get(format!("/cauldron/price/{}/at/{}", token_id, timestamp))
|
||||
.dispatch();
|
||||
|
||||
// Expect that we could not handle the calculation/conversion.
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue