@@ -10,7 +10,10 @@ use soroban_sdk::{
1010 Address , BytesN , Env , IntoVal , String , Symbol , TryFromVal ,
1111} ;
1212
13- use crate :: { InvoiceContract , InvoiceContractClient , InvoiceStatus , TTL_EXTEND_TO , TTL_THRESHOLD } ;
13+ use crate :: {
14+ InvoiceContract , InvoiceContractClient , InvoiceStatus , MAX_FACE_VALUE , TTL_EXTEND_TO ,
15+ TTL_THRESHOLD ,
16+ } ;
1417
1518// Default invoice parameters used across tests.
1619// These computed constants eliminate magic numbers in test assertions
@@ -92,8 +95,20 @@ pub struct MockPool;
9295
9396#[ contractimpl]
9497impl MockPool {
95- pub fn handle_default ( _env : Env , _invoice_id : BytesN < 32 > ) -> bool {
96- true
98+ pub fn set_return_values ( env : Env , handle_default : bool , repayment : bool ) {
99+ env. storage ( )
100+ . instance ( )
101+ . set ( & Symbol :: new ( & env, "handle_default" ) , & handle_default) ;
102+ env. storage ( )
103+ . instance ( )
104+ . set ( & Symbol :: new ( & env, "repayment" ) , & repayment) ;
105+ }
106+
107+ pub fn handle_default ( env : Env , _invoice_id : BytesN < 32 > ) -> bool {
108+ env. storage ( )
109+ . instance ( )
110+ . get ( & Symbol :: new ( & env, "handle_default" ) )
111+ . unwrap_or ( true )
97112 }
98113
99114 pub fn receive_repayment ( _env : Env , _invoice_id : BytesN < 32 > , _amount : u128 ) -> bool {
@@ -114,7 +129,10 @@ impl MockPool {
114129 ) -> bool {
115130 let key = Symbol :: new ( & env, "last_refund" ) ;
116131 env. storage ( ) . instance ( ) . set ( & key, & refund) ;
117- true
132+ env. storage ( )
133+ . instance ( )
134+ . get ( & Symbol :: new ( & env, "repayment" ) )
135+ . unwrap_or ( true )
118136 }
119137
120138 pub fn get_last_refund ( env : Env ) -> u128 {
@@ -701,6 +719,26 @@ fn test_create_fails_zero_face_value() {
701719 client. create ( & issuer, & buyer, & 0 , & due_date, & usdc) ;
702720}
703721
722+ #[ test]
723+ fn test_create_succeeds_at_max_face_value ( ) {
724+ let ( env, client, issuer, buyer, _, usdc) = setup ( ) ;
725+ let due_date = env. ledger ( ) . timestamp ( ) + DEFAULT_DUE_OFFSET ;
726+
727+ let invoice_id = client. create ( & issuer, & buyer, & MAX_FACE_VALUE , & due_date, & usdc) ;
728+
729+ assert_eq ! ( client. get( & invoice_id) . face_value, MAX_FACE_VALUE ) ;
730+ }
731+
732+ #[ test]
733+ #[ should_panic( expected = "Error(Contract, #16)" ) ]
734+ fn test_create_fails_above_max_face_value ( ) {
735+ let ( env, client, issuer, buyer, _, usdc) = setup ( ) ;
736+ let due_date = env. ledger ( ) . timestamp ( ) + DEFAULT_DUE_OFFSET ;
737+ let above_max = MAX_FACE_VALUE . checked_add ( 1 ) . unwrap ( ) ;
738+
739+ client. create ( & issuer, & buyer, & above_max, & due_date, & usdc) ;
740+ }
741+
704742#[ test]
705743#[ should_panic( expected = "Error(Contract, #7)" ) ]
706744fn test_create_fails_past_due_date ( ) {
@@ -2232,6 +2270,12 @@ impl MockEscrow {
22322270 . set ( & Symbol :: new ( & env, "asset" ) , & asset) ;
22332271 }
22342272
2273+ pub fn set_release_result ( env : Env , result : bool ) {
2274+ env. storage ( )
2275+ . instance ( )
2276+ . set ( & Symbol :: new ( & env, "release_result" ) , & result) ;
2277+ }
2278+
22352279 /// Minimal stub: transfers `amount` from escrow to pool.
22362280 /// No pool auth required — mirrors the real escrow's updated behavior
22372281 /// where the invoice contract (not the pool) is the caller.
@@ -2251,7 +2295,10 @@ impl MockEscrow {
22512295 let token_client = token:: Client :: new ( & env, & asset) ;
22522296 token_client. transfer ( & escrow_addr, & pool, & ( amount as i128 ) ) ;
22532297
2254- true
2298+ env. storage ( )
2299+ . instance ( )
2300+ . get ( & Symbol :: new ( & env, "release_result" ) )
2301+ . unwrap_or ( true )
22552302 }
22562303}
22572304
@@ -2264,6 +2311,33 @@ fn mock_escrow_for_pool(env: &Env, pool_id: &Address, asset: &Address) -> Addres
22642311 escrow_id
22652312}
22662313
2314+ fn setup_confirmed_invoice ( ) -> (
2315+ Env ,
2316+ InvoiceContractClient < ' static > ,
2317+ Address ,
2318+ BytesN < 32 > ,
2319+ Address ,
2320+ Address ,
2321+ ) {
2322+ let ( env, client, issuer, buyer, _, usdc) = setup ( ) ;
2323+ let due_date = env. ledger ( ) . timestamp ( ) + DEFAULT_DUE_OFFSET ;
2324+ let invoice_id = client. create ( & issuer, & buyer, & DEFAULT_FACE_VALUE , & due_date, & usdc) ;
2325+ attest ( & env, & client, & invoice_id) ;
2326+ client. list_for_financing ( & invoice_id, & DEFAULT_DISCOUNT_BPS ) ;
2327+
2328+ let pool = mock_pool_with_asset ( & env, & usdc) ;
2329+ client. set_pool_contract ( & pool) ;
2330+ let escrow = mock_escrow_for_pool ( & env, & pool, & usdc) ;
2331+ client. set_escrow_contract ( & escrow) ;
2332+ client. mark_funded ( & invoice_id, & pool, & usdc, & DEFAULT_FUNDED_AMOUNT ) ;
2333+ client. mark_shipped ( & invoice_id) ;
2334+ client. confirm_delivery ( & invoice_id, & issuer) ;
2335+ client. confirm_delivery ( & invoice_id, & buyer) ;
2336+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2337+
2338+ ( env, client, buyer, invoice_id, pool, escrow)
2339+ }
2340+
22672341// ============== SUPPORTED ASSET TESTS ==============
22682342
22692343#[ test]
@@ -2288,6 +2362,107 @@ fn test_add_supported_asset() {
22882362
22892363// ============================== REPAY TESTS ==============================
22902364
2365+ #[ test]
2366+ fn test_repay_rejects_false_escrow_result ( ) {
2367+ let ( env, client, buyer, invoice_id, _pool, escrow) = setup_confirmed_invoice ( ) ;
2368+ MockEscrowClient :: new ( & env, & escrow) . set_release_result ( & false ) ;
2369+ mint_tokens (
2370+ & env,
2371+ & client. get_funding_asset ( & invoice_id) ,
2372+ & buyer,
2373+ DEFAULT_FACE_VALUE as i128 ,
2374+ ) ;
2375+
2376+ let result = env. try_invoke_contract :: < bool , soroban_sdk:: Error > (
2377+ & client. address ,
2378+ & Symbol :: new ( & env, "repay" ) ,
2379+ ( invoice_id. clone ( ) , ) . into_val ( & env) ,
2380+ ) ;
2381+
2382+ assert ! ( result. is_err( ) ) ;
2383+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2384+ }
2385+
2386+ #[ test]
2387+ fn test_repay_rejects_false_pool_result ( ) {
2388+ let ( env, client, buyer, invoice_id, pool, _escrow) = setup_confirmed_invoice ( ) ;
2389+ MockPoolClient :: new ( & env, & pool) . set_return_values ( & true , & false ) ;
2390+ mint_tokens (
2391+ & env,
2392+ & client. get_funding_asset ( & invoice_id) ,
2393+ & buyer,
2394+ DEFAULT_FACE_VALUE as i128 ,
2395+ ) ;
2396+
2397+ let result = env. try_invoke_contract :: < bool , soroban_sdk:: Error > (
2398+ & client. address ,
2399+ & Symbol :: new ( & env, "repay" ) ,
2400+ ( invoice_id. clone ( ) , ) . into_val ( & env) ,
2401+ ) ;
2402+
2403+ assert ! ( result. is_err( ) ) ;
2404+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2405+ }
2406+
2407+ #[ test]
2408+ fn test_repay_early_rejects_false_escrow_result ( ) {
2409+ let ( env, client, buyer, invoice_id, _pool, escrow) = setup_confirmed_invoice ( ) ;
2410+ MockEscrowClient :: new ( & env, & escrow) . set_release_result ( & false ) ;
2411+ mint_tokens (
2412+ & env,
2413+ & client. get_funding_asset ( & invoice_id) ,
2414+ & buyer,
2415+ DEFAULT_FACE_VALUE as i128 ,
2416+ ) ;
2417+
2418+ let result = env. try_invoke_contract :: < bool , soroban_sdk:: Error > (
2419+ & client. address ,
2420+ & Symbol :: new ( & env, "repay_early" ) ,
2421+ ( invoice_id. clone ( ) , ) . into_val ( & env) ,
2422+ ) ;
2423+
2424+ assert ! ( result. is_err( ) ) ;
2425+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2426+ }
2427+
2428+ #[ test]
2429+ fn test_repay_early_rejects_false_pool_result ( ) {
2430+ let ( env, client, buyer, invoice_id, pool, _escrow) = setup_confirmed_invoice ( ) ;
2431+ MockPoolClient :: new ( & env, & pool) . set_return_values ( & true , & false ) ;
2432+ mint_tokens (
2433+ & env,
2434+ & client. get_funding_asset ( & invoice_id) ,
2435+ & buyer,
2436+ DEFAULT_FACE_VALUE as i128 ,
2437+ ) ;
2438+
2439+ let result = env. try_invoke_contract :: < bool , soroban_sdk:: Error > (
2440+ & client. address ,
2441+ & Symbol :: new ( & env, "repay_early" ) ,
2442+ ( invoice_id. clone ( ) , ) . into_val ( & env) ,
2443+ ) ;
2444+
2445+ assert ! ( result. is_err( ) ) ;
2446+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2447+ }
2448+
2449+ #[ test]
2450+ fn test_trigger_default_rejects_false_pool_result ( ) {
2451+ let ( env, client, _buyer, invoice_id, pool, _escrow) = setup_confirmed_invoice ( ) ;
2452+ MockPoolClient :: new ( & env, & pool) . set_return_values ( & false , & true ) ;
2453+ let due_date = client. get ( & invoice_id) . due_date ;
2454+ env. ledger ( ) . set_timestamp ( due_date) ;
2455+
2456+ let result = env. try_invoke_contract :: < bool , soroban_sdk:: Error > (
2457+ & client. address ,
2458+ & Symbol :: new ( & env, "trigger_default" ) ,
2459+ ( invoice_id. clone ( ) , ) . into_val ( & env) ,
2460+ ) ;
2461+
2462+ assert ! ( result. is_err( ) ) ;
2463+ assert_eq ! ( client. get( & invoice_id) . status, InvoiceStatus :: Confirmed ) ;
2464+ }
2465+
22912466#[ test]
22922467fn test_repay_from_confirmed ( ) {
22932468 let ( env, client, issuer, buyer, _, usdc) = setup ( ) ;
0 commit comments