use crate::app::context::AppContext;
use crate::db::is_user_present;
use crate::util::send_dm;
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
pub async fn last_trade_index(
ctx: &AppContext,
msg: Message,
event: &UnwrappedMessage,
my_keys: &Keys,
) -> Result<(), MostroError> {
let pool = ctx.pool();
let requester_pubkey = event.identity.to_string();
let trade_key = event.sender;
let request_id = msg.get_inner_message_kind().request_id;
let user = match is_user_present(pool, requester_pubkey).await {
Ok(user) => user,
Err(_) => {
return Err(MostroCantDo(CantDoReason::NotFound));
}
};
if user.last_trade_index == 0 {
return Err(MostroCantDo(CantDoReason::InvalidTradeIndex));
}
let kind = MessageKind::new(
None,
request_id,
Some(user.last_trade_index),
Action::LastTradeIndex,
None,
);
let last_trade_index_message = Message::Restore(kind);
let message_json = last_trade_index_message
.as_json()
.map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?;
tracing::info!(
"User with pubkey: {} requested last trade index",
user.pubkey
);
tracing::info!("Last trade index: {}", user.last_trade_index);
if let Err(e) = send_dm(trade_key, my_keys, &message_json, None).await {
tracing::error!("Error sending message with last trade index: {:?}", e);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use nostr_sdk::{Keys, Timestamp};
use sqlx::sqlite::SqlitePoolOptions;
use sqlx::SqlitePool;
fn create_test_keys() -> Keys {
Keys::generate()
}
fn create_test_unwrapped_message(sender_keys: &Keys) -> UnwrappedMessage {
let trade = create_test_keys();
println!(
"Creating UnwrappedMessage with identity pubkey: {}",
sender_keys.public_key()
);
UnwrappedMessage {
message: Message::Restore(MessageKind::new(
None,
Some(1),
None,
Action::LastTradeIndex,
None,
)),
signature: None,
sender: trade.public_key(),
identity: sender_keys.public_key(),
created_at: Timestamp::now(),
}
}
async fn setup_test_db() -> SqlitePool {
let pool = SqlitePoolOptions::new()
.max_connections(1)
.connect(":memory:")
.await
.unwrap();
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS users (
pubkey CHAR(64) PRIMARY KEY NOT NULL,
is_admin INTEGER NOT NULL DEFAULT 0,
admin_password CHAR(64),
is_solver INTEGER NOT NULL DEFAULT 0,
is_banned INTEGER NOT NULL DEFAULT 0,
category INTEGER NOT NULL DEFAULT 0,
last_trade_index INTEGER NOT NULL DEFAULT 0,
total_reviews INTEGER NOT NULL DEFAULT 0,
total_rating REAL NOT NULL DEFAULT 0.0,
last_rating INTEGER NOT NULL DEFAULT 0,
max_rating INTEGER NOT NULL DEFAULT 0,
min_rating INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT 0
)
"#,
)
.execute(&pool)
.await
.unwrap();
pool
}
async fn insert_test_user(pool: &SqlitePool, pubkey: &str, last_trade_index: i64) {
sqlx::query(
r#"
INSERT INTO users (pubkey, last_trade_index)
VALUES (?, ?)
"#,
)
.bind(pubkey)
.bind(last_trade_index)
.execute(pool)
.await
.unwrap();
}
#[tokio::test]
async fn test_last_trade_index_user_not_found() {
let pool = setup_test_db().await;
use crate::app::context::test_utils::{test_settings, TestContextBuilder};
let ctx = TestContextBuilder::new()
.with_pool(std::sync::Arc::new(pool.clone()))
.with_settings(test_settings())
.build();
let sender_keys = create_test_keys();
let event = create_test_unwrapped_message(&sender_keys);
let kind = MessageKind::new(None, Some(1234567890), None, Action::LastTradeIndex, None);
let result = last_trade_index(&ctx, Message::Restore(kind), &event, &sender_keys).await;
assert!(
result.is_err(),
"Should return error when user doesn't exist"
);
match result {
Err(MostroError::MostroCantDo(CantDoReason::NotFound)) => {
}
Err(e) => {
panic!("Expected MostroInternalErr(DbAccessError), got: {:?}", e);
}
Ok(_) => {
panic!("Should have failed when user doesn't exist");
}
}
}
#[tokio::test]
async fn test_last_trade_index_correct_value() {
let pool = setup_test_db().await;
let user1_keys = create_test_keys();
let user1_pubkey = user1_keys.public_key().to_string();
insert_test_user(&pool, &user1_pubkey, 10).await;
let user2_keys = create_test_keys();
let user2_pubkey = user2_keys.public_key().to_string();
insert_test_user(&pool, &user2_pubkey, 99).await;
let user1 = is_user_present(&pool, user1_pubkey.clone()).await.unwrap();
assert_eq!(
user1.last_trade_index, 10,
"User 1 should have last_trade_index = 10"
);
let user2 = is_user_present(&pool, user2_pubkey.clone()).await.unwrap();
assert_eq!(
user2.last_trade_index, 99,
"User 2 should have last_trade_index = 99"
);
let message = MessageKind::new(
None,
None,
Some(user1.last_trade_index),
Action::LastTradeIndex,
None,
);
assert_eq!(
message.trade_index(),
10i64,
"Message should contain correct trade_index"
);
}
#[tokio::test]
async fn test_last_trade_index_message_serialization() {
let pool = setup_test_db().await;
let sender_keys = create_test_keys();
let pubkey = sender_keys.public_key().to_string();
let trade_index = 42i64;
insert_test_user(&pool, &pubkey, trade_index).await;
let user = is_user_present(&pool, pubkey).await.unwrap();
let message = MessageKind::new(
None,
None,
Some(user.last_trade_index),
Action::LastTradeIndex,
None,
);
let json_result = message.as_json();
assert!(json_result.is_ok(), "Message serialization should succeed");
let json = json_result.unwrap();
assert!(
json.contains("last-trade-index"),
"JSON should contain action field"
);
assert!(
json.contains("trade_index"),
"JSON should contain trade_index field"
);
}
}