@@ -996,3 +996,176 @@ fn test_approve_sale_fee_routing_with_royalties() {
996996 "Pending sale should be deleted after approval"
997997 ) ;
998998}
999+
1000+ #[ test]
1001+ fn test_approve_sale_with_existing_pending_sale ( ) {
1002+ let mut app = setup_app_with_balances ( ) ;
1003+ let minter = app. api ( ) . addr_make ( "minter" ) ;
1004+ let seller = app. api ( ) . addr_make ( "seller" ) ;
1005+ let buyer1 = app. api ( ) . addr_make ( "buyer1" ) ;
1006+ let buyer2 = app. api ( ) . addr_make ( "buyer2" ) ;
1007+ let manager = app. api ( ) . addr_make ( "manager" ) ;
1008+
1009+ // Give funds to buyer1 and buyer2
1010+ use cw_multi_test:: { BankSudo , SudoMsg } ;
1011+ let funds = vec ! [ coin( 10000 , "uxion" ) ] ;
1012+ app. sudo ( SudoMsg :: Bank ( BankSudo :: Mint {
1013+ to_address : buyer1. to_string ( ) ,
1014+ amount : funds. clone ( ) ,
1015+ } ) )
1016+ . unwrap ( ) ;
1017+ app. sudo ( SudoMsg :: Bank ( BankSudo :: Mint {
1018+ to_address : buyer2. to_string ( ) ,
1019+ amount : funds. clone ( ) ,
1020+ } ) )
1021+ . unwrap ( ) ;
1022+
1023+ let asset_contract = setup_asset_contract ( & mut app, & minter) ;
1024+ let marketplace_contract = setup_marketplace_with_approvals ( & mut app, & manager) ;
1025+
1026+ // Use a unique token_id with timestamp to avoid any potential conflicts
1027+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
1028+ let timestamp = SystemTime :: now ( )
1029+ . duration_since ( UNIX_EPOCH )
1030+ . unwrap ( )
1031+ . as_nanos ( ) ;
1032+ let token_id = format ! ( "test_approve_existing_pending_{}" , timestamp) ;
1033+ mint_nft ( & mut app, & asset_contract, & minter, & seller, & token_id) ;
1034+
1035+ let price = coin ( 100 , "uxion" ) ;
1036+ let listing_id = create_listing_helper (
1037+ & mut app,
1038+ & marketplace_contract,
1039+ & asset_contract,
1040+ & seller,
1041+ & token_id,
1042+ price. clone ( ) ,
1043+ ) ;
1044+
1045+ // First buyer creates a pending sale
1046+ let buy_msg1 = ExecuteMsg :: BuyItem {
1047+ listing_id : listing_id. clone ( ) ,
1048+ price : price. clone ( ) ,
1049+ } ;
1050+
1051+ let buy_result1 = app. execute_contract (
1052+ buyer1. clone ( ) ,
1053+ marketplace_contract. clone ( ) ,
1054+ & buy_msg1,
1055+ std:: slice:: from_ref ( & price) ,
1056+ ) ;
1057+
1058+ assert ! (
1059+ buy_result1. is_ok( ) ,
1060+ "First buy should succeed: {:?}" ,
1061+ buy_result1. as_ref( ) . unwrap_err( )
1062+ ) ;
1063+
1064+ let pending_sale_id1 = buy_result1
1065+ . unwrap ( )
1066+ . events
1067+ . iter ( )
1068+ . find ( |e| e. ty == "wasm-xion-nft-marketplace/pending-sale-created" )
1069+ . unwrap ( )
1070+ . attributes
1071+ . iter ( )
1072+ . find ( |a| a. key == "id" )
1073+ . unwrap ( )
1074+ . value
1075+ . clone ( ) ;
1076+
1077+ // Verify first pending sale exists
1078+ let pending_sale1: PendingSale = app
1079+ . wrap ( )
1080+ . query_wasm_smart (
1081+ marketplace_contract. clone ( ) ,
1082+ & QueryMsg :: PendingSale {
1083+ id : pending_sale_id1. clone ( ) ,
1084+ } ,
1085+ )
1086+ . unwrap ( ) ;
1087+ assert_eq ! ( pending_sale1. buyer, buyer1) ;
1088+ assert_eq ! ( pending_sale1. token_id, token_id. clone( ) ) ;
1089+
1090+ // Second buyer tries to create another pending sale for the same item - should fail
1091+ let buy_msg2 = ExecuteMsg :: BuyItem {
1092+ listing_id : listing_id. clone ( ) ,
1093+ price : price. clone ( ) ,
1094+ } ;
1095+
1096+ let buy_result2 = app. execute_contract (
1097+ buyer2. clone ( ) ,
1098+ marketplace_contract. clone ( ) ,
1099+ & buy_msg2,
1100+ std:: slice:: from_ref ( & price) ,
1101+ ) ;
1102+
1103+ assert ! ( buy_result2. is_err( ) ) ;
1104+ assert_error (
1105+ buy_result2,
1106+ xion_nft_marketplace:: error:: ContractError :: PendingSaleAlreadyExists {
1107+ collection : asset_contract. to_string ( ) ,
1108+ token_id : token_id. clone ( ) ,
1109+ }
1110+ . to_string ( ) ,
1111+ ) ;
1112+
1113+ // Now approve the first pending sale
1114+ let approve_msg = ExecuteMsg :: ApproveSale {
1115+ id : pending_sale_id1. clone ( ) ,
1116+ } ;
1117+
1118+ let approve_result = app. execute_contract (
1119+ manager. clone ( ) ,
1120+ marketplace_contract. clone ( ) ,
1121+ & approve_msg,
1122+ & [ ] ,
1123+ ) ;
1124+
1125+ assert ! (
1126+ approve_result. is_ok( ) ,
1127+ "Approval should succeed even with existing pending sale"
1128+ ) ;
1129+
1130+ let events = approve_result. unwrap ( ) . events ;
1131+ let approved_event = events
1132+ . iter ( )
1133+ . find ( |e| e. ty == "wasm-xion-nft-marketplace/sale-approved" ) ;
1134+ assert ! (
1135+ approved_event. is_some( ) ,
1136+ "Sale approved event should be emitted"
1137+ ) ;
1138+
1139+ // Verify the pending sale is removed after approval
1140+ let pending_sale_query = app. wrap ( ) . query_wasm_smart :: < PendingSale > (
1141+ marketplace_contract. clone ( ) ,
1142+ & QueryMsg :: PendingSale {
1143+ id : pending_sale_id1,
1144+ } ,
1145+ ) ;
1146+ assert ! (
1147+ pending_sale_query. is_err( ) ,
1148+ "Pending sale should be removed after approval"
1149+ ) ;
1150+
1151+ // Verify the listing is removed
1152+ let listing_query = app. wrap ( ) . query_wasm_smart :: < Listing > (
1153+ marketplace_contract. clone ( ) ,
1154+ & QueryMsg :: Listing { listing_id } ,
1155+ ) ;
1156+ assert ! (
1157+ listing_query. is_err( ) ,
1158+ "Listing should be deleted after approval"
1159+ ) ;
1160+
1161+ // Verify NFT ownership transferred to buyer1
1162+ let owner_query = OwnerQueryMsg :: OwnerOf {
1163+ token_id : token_id. clone ( ) ,
1164+ include_expired : Some ( false ) ,
1165+ } ;
1166+ let owner_resp: cw721:: msg:: OwnerOfResponse = app
1167+ . wrap ( )
1168+ . query_wasm_smart ( asset_contract. clone ( ) , & owner_query)
1169+ . unwrap ( ) ;
1170+ assert_eq ! ( owner_resp. owner, buyer1. to_string( ) ) ;
1171+ }
0 commit comments