From 7eada9588cc1dbc5bfe261063e1c70f7df466f07 Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Tue, 9 Sep 2025 02:45:56 +0000 Subject: [PATCH] tests for bidding when auction is expired --- src/bidding/confirm_bid_amount_callback.rs | 95 +++++++++++++++++++++- src/bot_message_sender.rs | 20 +++++ src/commands/new_listing/handlers.rs | 2 +- src/db/dao/listing_dao.rs | 4 +- src/message/mod.rs | 4 + 5 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/bidding/confirm_bid_amount_callback.rs b/src/bidding/confirm_bid_amount_callback.rs index b0c12ad..99de8d7 100644 --- a/src/bidding/confirm_bid_amount_callback.rs +++ b/src/bidding/confirm_bid_amount_callback.rs @@ -6,6 +6,7 @@ use crate::{ App, BotError, BotResult, RootDialogue, }; use anyhow::{anyhow, Context}; +use chrono::Utc; use itertools::Itertools; use log::{error, info}; use teloxide::types::*; @@ -84,9 +85,14 @@ pub async fn handle_awaiting_confirm_bid_amount_callback( } }; + if listing.base.ends_at < Utc::now() { + return app + .send_message(MessageType::BidInvalidListingExpired { listing, buyer }) + .await; + } + let bid = NewBid::new_basic(listing.persisted.id, buyer.persisted.id, bid_amount); let bid = app.daos.bid.insert_bid(&bid).await?; - dialogue.exit().await.context("failed to exit dialogue")?; app.send_message(MessageType::BidHasBeenConfirmedForBuyer { @@ -132,12 +138,22 @@ pub async fn handle_awaiting_confirm_bid_amount_callback( mod tests { use super::*; use crate::{db::DAOs, message_sender::MockMessageSender, test_utils::*}; - use dptree::{deps, di::Injectable}; + use chrono::{Duration, Utc}; + use dptree::{ + deps, + di::{DependencyMap, Injectable}, + }; use mockall::predicate::function; use std::str::FromStr; - #[tokio::test] - async fn test_confirm_bid_amount() { + struct Fixtures { + deps: DependencyMap, + seller: PersistedUser, + buyer: PersistedUser, + prev_buyer: PersistedUser, + listing: PersistedListing, + } + async fn set_up_fixtures() -> Fixtures { let deps = create_deps().await; let seller = with_test_user(&deps, |seller| { seller.username = Some("seller".to_string()); @@ -155,6 +171,77 @@ mod tests { }) .await; let listing = with_test_listing(&deps, &seller, |_| {}).await; + Fixtures { + deps, + seller, + buyer, + prev_buyer, + listing, + } + } + + #[tokio::test] + async fn test_confirm_bid_with_expired_listing() { + let Fixtures { + deps, + buyer, + mut listing, + .. + } = set_up_fixtures().await; + + // listing has already expired + listing.base.ends_at = Utc::now() - Duration::days(1); + let listing = deps + .get::() + .listing + .update_listing(&listing) + .await + .unwrap(); + + let cb_query = create_tele_callback_query( + "confirm_bid", + create_tele_user(|user| user.id = buyer.telegram_id.into()), + ); + + let mut message_sender = MockMessageSender::new(); + { + let l = listing.clone(); + let b = buyer.clone(); + message_sender + .expect_send_message() + .once() + .with(function(move |m| match m { + MessageType::BidInvalidListingExpired { listing, buyer } => { + assert_eq!(listing, &l); + assert_eq!(buyer, &b); + true + } + _ => false, + })) + .returning(|_| Ok(())); + } + let deps = with_message_sender(deps, message_sender).await; + + let mut deps = with_dialogue(deps, &buyer).await; + deps.insert_container(deps![ + listing, + cb_query, + buyer, + MoneyAmount::from_str("100.00").unwrap() + ]); + let ret = handle_awaiting_confirm_bid_amount_callback.inject(&deps)().await; + assert!(ret.is_ok(), "{ret:?}"); + } + + #[tokio::test] + async fn test_confirm_bid_amount() { + let Fixtures { + deps, + seller, + buyer, + prev_buyer, + listing, + } = set_up_fixtures().await; deps.get::() .bid diff --git a/src/bot_message_sender.rs b/src/bot_message_sender.rs index 64fc458..0c54737 100644 --- a/src/bot_message_sender.rs +++ b/src/bot_message_sender.rs @@ -103,6 +103,10 @@ impl MessageSender for BotMessageSender { MessageType::BidHasBeenConfirmedForBuyer { listing, bid } => { self.send_bid_has_been_confirmed(listing, bid).await?; } + MessageType::BidInvalidListingExpired { listing, buyer } => { + self.send_bid_invalid_listing_expired(listing, buyer) + .await?; + } } Ok(()) } @@ -165,4 +169,20 @@ impl BotMessageSender { ) .await } + + async fn send_bid_invalid_listing_expired( + &self, + listing: PersistedListing, + buyer: PersistedUser, + ) -> BotResult { + self.with_target(buyer.into()) + .send_html_message( + format!( + "Auction {title} has already ended", + title = listing.base.title + ), + None, + ) + .await + } } diff --git a/src/commands/new_listing/handlers.rs b/src/commands/new_listing/handlers.rs index e53f003..b90a8f2 100644 --- a/src/commands/new_listing/handlers.rs +++ b/src/commands/new_listing/handlers.rs @@ -262,7 +262,7 @@ pub async fn enter_edit_listing_draft( async fn save_listing(listing_dao: &ListingDAO, draft: ListingDraft) -> BotResult { let (listing, success_message) = if let Some(fields) = draft.persisted { let listing = listing_dao - .update_listing(PersistedListing { + .update_listing(&PersistedListing { persisted: fields, base: draft.base, fields: draft.fields, diff --git a/src/db/dao/listing_dao.rs b/src/db/dao/listing_dao.rs index c5ba09b..f257145 100644 --- a/src/db/dao/listing_dao.rs +++ b/src/db/dao/listing_dao.rs @@ -54,7 +54,7 @@ impl ListingDAO { Ok(FromRow::from_row(&row)?) } - pub async fn update_listing(&self, listing: PersistedListing) -> Result { + pub async fn update_listing(&self, listing: &PersistedListing) -> Result { let now = Utc::now(); let binds = binds_for_listing(&listing).push("updated_at", &now); @@ -123,6 +123,8 @@ fn binds_for_base(base: &ListingBase) -> BindFields { .push("title", &base.title) .push("description", &base.description) .push("currency_type", &base.currency_type) + .push("starts_at", &base.starts_at) + .push("ends_at", &base.ends_at) } fn binds_for_fields(fields: &ListingFields) -> BindFields { diff --git a/src/message/mod.rs b/src/message/mod.rs index 1f1ecea..5100fef 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -16,4 +16,8 @@ pub enum MessageType { listing: PersistedListing, bid: PersistedBid, }, + BidInvalidListingExpired { + listing: PersistedListing, + buyer: PersistedUser, + }, }