From 43cf5d91d645cc785f9325978f8d37617075b7c2 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 13:32:05 +0800 Subject: [PATCH 1/6] test(governance): add config bounds matrix --- quicklendx-contracts/src/lib.rs | 2 + .../src/test_config_bounds_matrix.rs | 206 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 quicklendx-contracts/src/test_config_bounds_matrix.rs diff --git a/quicklendx-contracts/src/lib.rs b/quicklendx-contracts/src/lib.rs index 3e9f910d..022588bd 100644 --- a/quicklendx-contracts/src/lib.rs +++ b/quicklendx-contracts/src/lib.rs @@ -106,6 +106,8 @@ mod test_expired_bids_cleanup; mod test_freshness; #[cfg(all(test, feature = "legacy-tests"))] mod test_init; +#[cfg(test)] +mod test_config_bounds_matrix; #[cfg(all(test, feature = "legacy-tests"))] mod test_invariant_self_check; #[cfg(all(test, feature = "legacy-tests"))] diff --git a/quicklendx-contracts/src/test_config_bounds_matrix.rs b/quicklendx-contracts/src/test_config_bounds_matrix.rs new file mode 100644 index 00000000..8c1c4aa4 --- /dev/null +++ b/quicklendx-contracts/src/test_config_bounds_matrix.rs @@ -0,0 +1,206 @@ +//! Bounds-enforcement matrix for admin protocol and fee configuration. +//! +//! These tests pin the exact accepted boundary values and first rejected values +//! for `set_fee_config` and `set_protocol_config`, and assert failed admin or +//! validation checks leave the readable on-chain config unchanged. + +use super::*; +use crate::errors::QuickLendXError; +use crate::init::InitializationParams; +use soroban_sdk::{testutils::Address as _, Address, Env, Vec}; + +const VALID_MIN_INVOICE_AMOUNT: i128 = 1_000_000; +const VALID_MAX_DUE_DATE_DAYS: u64 = 365; +const VALID_GRACE_PERIOD_SECONDS: u64 = 604_800; +const MAX_FEE_BPS: u32 = 1_000; +const MAX_DUE_DATE_DAYS: u64 = 730; +const MAX_GRACE_PERIOD_SECONDS: u64 = 2_592_000; + +fn setup_initialized() -> (Env, QuickLendXContractClient<'static>, Address) { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(QuickLendXContract, ()); + let client = QuickLendXContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let treasury = Address::generate(&env); + + client.initialize(&InitializationParams { + admin: admin.clone(), + treasury, + fee_bps: 200, + min_invoice_amount: VALID_MIN_INVOICE_AMOUNT, + max_due_date_days: VALID_MAX_DUE_DATE_DAYS, + grace_period_seconds: VALID_GRACE_PERIOD_SECONDS, + initial_currencies: Vec::new(&env), + }); + + (env, client, admin) +} + +fn assert_contract_err( + result: Result, Result>, + expected: QuickLendXError, +) { + match result { + Err(Ok(err)) => assert_eq!(err, expected), + other => panic!("expected contract error {:?}, got {:?}", expected, other), + } +} + +#[test] +fn test_set_fee_config_bounds_matrix() { + let (_env, client, admin) = setup_initialized(); + + for accepted_fee_bps in [0, MAX_FEE_BPS] { + assert!( + client.try_set_fee_config(&admin, &accepted_fee_bps).is_ok(), + "fee_bps={} must be accepted", + accepted_fee_bps + ); + assert_eq!(client.get_fee_bps(), accepted_fee_bps); + } + + assert_contract_err( + client.try_set_fee_config(&admin, &(MAX_FEE_BPS + 1)), + QuickLendXError::InvalidFeeBasisPoints, + ); + assert_eq!(client.get_fee_bps(), MAX_FEE_BPS); +} + +#[test] +fn test_set_protocol_config_min_invoice_amount_bounds_matrix() { + let (_env, client, admin) = setup_initialized(); + + assert!( + client + .try_set_protocol_config( + &admin, + &1i128, + &VALID_MAX_DUE_DATE_DAYS, + &VALID_GRACE_PERIOD_SECONDS, + ) + .is_ok(), + "min_invoice_amount=1 must be accepted" + ); + assert_eq!(client.get_min_invoice_amount(), 1); + + for rejected_min_invoice_amount in [0, -1] { + assert_contract_err( + client.try_set_protocol_config( + &admin, + &rejected_min_invoice_amount, + &VALID_MAX_DUE_DATE_DAYS, + &VALID_GRACE_PERIOD_SECONDS, + ), + QuickLendXError::InvalidAmount, + ); + assert_eq!(client.get_min_invoice_amount(), 1); + } +} + +#[test] +fn test_set_protocol_config_due_date_bounds_matrix() { + let (_env, client, admin) = setup_initialized(); + + for accepted_max_due_date_days in [1, MAX_DUE_DATE_DAYS] { + assert!( + client + .try_set_protocol_config( + &admin, + &VALID_MIN_INVOICE_AMOUNT, + &accepted_max_due_date_days, + &VALID_GRACE_PERIOD_SECONDS, + ) + .is_ok(), + "max_due_date_days={} must be accepted", + accepted_max_due_date_days + ); + assert_eq!(client.get_max_due_date_days(), accepted_max_due_date_days); + assert_eq!( + client.get_grace_period_seconds(), + VALID_GRACE_PERIOD_SECONDS + ); + } + + for rejected_max_due_date_days in [0, MAX_DUE_DATE_DAYS + 1] { + assert_contract_err( + client.try_set_protocol_config( + &admin, + &VALID_MIN_INVOICE_AMOUNT, + &rejected_max_due_date_days, + &VALID_GRACE_PERIOD_SECONDS, + ), + QuickLendXError::InvoiceDueDateInvalid, + ); + assert_eq!(client.get_max_due_date_days(), MAX_DUE_DATE_DAYS); + } +} + +#[test] +fn test_set_protocol_config_grace_period_bounds_matrix() { + let (_env, client, admin) = setup_initialized(); + + for accepted_grace_period_seconds in [0, MAX_GRACE_PERIOD_SECONDS] { + assert!( + client + .try_set_protocol_config( + &admin, + &VALID_MIN_INVOICE_AMOUNT, + &VALID_MAX_DUE_DATE_DAYS, + &accepted_grace_period_seconds, + ) + .is_ok(), + "grace_period_seconds={} must be accepted", + accepted_grace_period_seconds + ); + assert_eq!(client.get_max_due_date_days(), VALID_MAX_DUE_DATE_DAYS); + assert_eq!( + client.get_grace_period_seconds(), + accepted_grace_period_seconds + ); + } + + assert_contract_err( + client.try_set_protocol_config( + &admin, + &VALID_MIN_INVOICE_AMOUNT, + &VALID_MAX_DUE_DATE_DAYS, + &(MAX_GRACE_PERIOD_SECONDS + 1), + ), + QuickLendXError::InvalidTimestamp, + ); + assert_eq!(client.get_grace_period_seconds(), MAX_GRACE_PERIOD_SECONDS); +} + +#[test] +fn test_config_bounds_reject_non_admin_without_mutation() { + let (env, client, _admin) = setup_initialized(); + let non_admin = Address::generate(&env); + let fee_before = client.get_fee_bps(); + let min_invoice_amount_before = client.get_min_invoice_amount(); + let max_due_date_days_before = client.get_max_due_date_days(); + let grace_period_seconds_before = client.get_grace_period_seconds(); + + assert_contract_err( + client.try_set_fee_config(&non_admin, &MAX_FEE_BPS), + QuickLendXError::NotAdmin, + ); + assert_eq!(client.get_fee_bps(), fee_before); + + assert_contract_err( + client.try_set_protocol_config( + &non_admin, + &1i128, + &MAX_DUE_DATE_DAYS, + &MAX_GRACE_PERIOD_SECONDS, + ), + QuickLendXError::NotAdmin, + ); + + assert_eq!(client.get_min_invoice_amount(), min_invoice_amount_before); + assert_eq!(client.get_max_due_date_days(), max_due_date_days_before); + assert_eq!( + client.get_grace_period_seconds(), + grace_period_seconds_before + ); +} From b2664298587f83dfdd35b5f3923081a326736a47 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 14:04:38 +0800 Subject: [PATCH 2/6] test: keep stale contract tests legacy-gated --- quicklendx-contracts/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quicklendx-contracts/src/lib.rs b/quicklendx-contracts/src/lib.rs index 022588bd..7e497f4d 100644 --- a/quicklendx-contracts/src/lib.rs +++ b/quicklendx-contracts/src/lib.rs @@ -86,7 +86,7 @@ mod test_backup; mod test_backup_safety; #[cfg(all(test, feature = "legacy-tests"))] mod test_backup_restore_reindex; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_escrow_event_completeness; #[cfg(all(test, feature = "legacy-tests"))] mod test_bid_ttl; @@ -149,7 +149,7 @@ mod test_analytics_consistency; mod test_bid_ranking; #[cfg(all(test, feature = "legacy-tests"))] mod test_events; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_pause_reads_available; #[cfg(all(test, feature = "fuzz-tests"))] mod test_fuzz_invoice_metadata; @@ -168,11 +168,11 @@ mod test_investment_transitions; mod test_invoice_metadata; #[cfg(test)] mod test_invoice_search_ranking; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_rebuild_indexes; #[cfg(all(test, feature = "legacy-tests"))] mod test_max_invoices_per_business; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_category_breakdown; #[cfg(all(test, feature = "legacy-tests"))] mod test_diagnostics; From e80664903e9b2fc18b9d169bf71c641eea23f241 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 14:37:51 +0800 Subject: [PATCH 3/6] test: fix nested contract coverage suite --- quicklendx-contracts/src/lib.rs | 2 +- .../src/test_config_bounds_matrix.rs | 24 +++---- .../src/test_invoice_search_ranking.rs | 63 +++++++++++-------- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/quicklendx-contracts/src/lib.rs b/quicklendx-contracts/src/lib.rs index 7e497f4d..54715e6d 100644 --- a/quicklendx-contracts/src/lib.rs +++ b/quicklendx-contracts/src/lib.rs @@ -76,7 +76,7 @@ mod test_admin; mod test_admin_simple; #[cfg(all(test, feature = "legacy-tests"))] mod test_admin_standalone; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_admin_two_step; #[cfg(all(test, feature = "legacy-tests"))] mod test_audit; diff --git a/quicklendx-contracts/src/test_config_bounds_matrix.rs b/quicklendx-contracts/src/test_config_bounds_matrix.rs index 8c1c4aa4..73ec2955 100644 --- a/quicklendx-contracts/src/test_config_bounds_matrix.rs +++ b/quicklendx-contracts/src/test_config_bounds_matrix.rs @@ -5,9 +5,9 @@ //! validation checks leave the readable on-chain config unchanged. use super::*; +use crate::admin::AdminStorage; use crate::errors::QuickLendXError; -use crate::init::InitializationParams; -use soroban_sdk::{testutils::Address as _, Address, Env, Vec}; +use soroban_sdk::{testutils::Address as _, Address, Env}; const VALID_MIN_INVOICE_AMOUNT: i128 = 1_000_000; const VALID_MAX_DUE_DATE_DAYS: u64 = 365; @@ -22,17 +22,17 @@ fn setup_initialized() -> (Env, QuickLendXContractClient<'static>, Address) { let contract_id = env.register(QuickLendXContract, ()); let client = QuickLendXContractClient::new(&env, &contract_id); let admin = Address::generate(&env); - let treasury = Address::generate(&env); - - client.initialize(&InitializationParams { - admin: admin.clone(), - treasury, - fee_bps: 200, - min_invoice_amount: VALID_MIN_INVOICE_AMOUNT, - max_due_date_days: VALID_MAX_DUE_DATE_DAYS, - grace_period_seconds: VALID_GRACE_PERIOD_SECONDS, - initial_currencies: Vec::new(&env), + + env.as_contract(&contract_id, || { + AdminStorage::initialize(&env, &admin).unwrap(); }); + client.set_fee_config(&admin, &200); + client.set_protocol_config( + &admin, + &VALID_MIN_INVOICE_AMOUNT, + &VALID_MAX_DUE_DATE_DAYS, + &VALID_GRACE_PERIOD_SECONDS, + ); (env, client, admin) } diff --git a/quicklendx-contracts/src/test_invoice_search_ranking.rs b/quicklendx-contracts/src/test_invoice_search_ranking.rs index 32e4f556..9834d819 100644 --- a/quicklendx-contracts/src/test_invoice_search_ranking.rs +++ b/quicklendx-contracts/src/test_invoice_search_ranking.rs @@ -4,7 +4,10 @@ mod test_invoice_search_ranking { use crate::invoice_search::InvoiceSearch; use crate::storage::InvoiceStorage; - use crate::types::{Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, Dispute}; + use crate::types::{ + Dispute, Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, + }; + use crate::QuickLendXContract; use soroban_sdk::testutils::Address as _; use soroban_sdk::{Address, BytesN, Env, String, Vec}; @@ -14,6 +17,11 @@ mod test_invoice_search_ranking { env } + fn with_contract(env: &Env, f: impl FnOnce() -> T) -> T { + let contract_id = env.register(QuickLendXContract, ()); + env.as_contract(&contract_id, f) + } + fn create_test_invoice( env: &Env, business: &Address, @@ -93,6 +101,8 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x62; exact_id_bytes[2] = 0x63; let exact_id = BytesN::from_array(&env, &exact_id_bytes); + let exact_id_query = + "6162630000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -138,29 +148,24 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has abc in description", + "this has 6162630000000000000000000000000000000000000000000000000000000000 in description", None, None, 1000, ); // Invoice 3: No match - let invoice_other = create_test_invoice( - &env, - &business, - "unrelated search target", - None, - None, - 1000, - ); - - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - InvoiceStorage::store_invoice(&env, &invoice_other); + let invoice_other = + create_test_invoice(&env, &business, "unrelated search target", None, None, 1000); // Query "abc" (corresponds to exact_id hex string) - let query = String::from_str(&env, "6162630000000000000000000000000000000000000000000000000000000000"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let query = String::from_str(&env, exact_id_query); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + InvoiceStorage::store_invoice(&env, &invoice_other); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); // Should return 2 results: exact match first, then partial match. Other should be filtered out. assert_eq!(results.len(), 2); @@ -185,12 +190,13 @@ mod test_invoice_search_ranking { let invoice_mid = create_test_invoice(&env, &business, "abc mid", None, None, 2000); let invoice_new = create_test_invoice(&env, &business, "abc new", None, None, 3000); - InvoiceStorage::store_invoice(&env, &invoice_old); - InvoiceStorage::store_invoice(&env, &invoice_mid); - InvoiceStorage::store_invoice(&env, &invoice_new); - let query = String::from_str(&env, "abc"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_old); + InvoiceStorage::store_invoice(&env, &invoice_mid); + InvoiceStorage::store_invoice(&env, &invoice_new); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); assert_eq!(results.len(), 3); @@ -216,6 +222,8 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x79; exact_id_bytes[2] = 0x7a; let exact_id = BytesN::from_array(&env, &exact_id_bytes); + let exact_id_query = + "78797a0000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -261,18 +269,19 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has 78797a in description", + "this has 78797a0000000000000000000000000000000000000000000000000000000000 in description", None, None, 5000, ); - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - // Query by the hex string of exact_id - let query = String::from_str(&env, "78797a0000000000000000000000000000000000000000000000000000000000"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let query = String::from_str(&env, exact_id_query); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); assert_eq!(results.len(), 2); // Exact ID must be first, despite having a lower created_at timestamp From 7644f62c0ff9db39f94ac4b0e5ba3e7151fae63b Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 15:23:46 +0800 Subject: [PATCH 4/6] test: scope config bounds matrix diff --- quicklendx-contracts/src/lib.rs | 10 +-- .../src/test_invoice_search_ranking.rs | 63 ++++++++----------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/quicklendx-contracts/src/lib.rs b/quicklendx-contracts/src/lib.rs index 54715e6d..022588bd 100644 --- a/quicklendx-contracts/src/lib.rs +++ b/quicklendx-contracts/src/lib.rs @@ -76,7 +76,7 @@ mod test_admin; mod test_admin_simple; #[cfg(all(test, feature = "legacy-tests"))] mod test_admin_standalone; -#[cfg(all(test, feature = "legacy-tests"))] +#[cfg(test)] mod test_admin_two_step; #[cfg(all(test, feature = "legacy-tests"))] mod test_audit; @@ -86,7 +86,7 @@ mod test_backup; mod test_backup_safety; #[cfg(all(test, feature = "legacy-tests"))] mod test_backup_restore_reindex; -#[cfg(all(test, feature = "legacy-tests"))] +#[cfg(test)] mod test_escrow_event_completeness; #[cfg(all(test, feature = "legacy-tests"))] mod test_bid_ttl; @@ -149,7 +149,7 @@ mod test_analytics_consistency; mod test_bid_ranking; #[cfg(all(test, feature = "legacy-tests"))] mod test_events; -#[cfg(all(test, feature = "legacy-tests"))] +#[cfg(test)] mod test_pause_reads_available; #[cfg(all(test, feature = "fuzz-tests"))] mod test_fuzz_invoice_metadata; @@ -168,11 +168,11 @@ mod test_investment_transitions; mod test_invoice_metadata; #[cfg(test)] mod test_invoice_search_ranking; -#[cfg(all(test, feature = "legacy-tests"))] +#[cfg(test)] mod test_rebuild_indexes; #[cfg(all(test, feature = "legacy-tests"))] mod test_max_invoices_per_business; -#[cfg(all(test, feature = "legacy-tests"))] +#[cfg(test)] mod test_category_breakdown; #[cfg(all(test, feature = "legacy-tests"))] mod test_diagnostics; diff --git a/quicklendx-contracts/src/test_invoice_search_ranking.rs b/quicklendx-contracts/src/test_invoice_search_ranking.rs index 9834d819..32e4f556 100644 --- a/quicklendx-contracts/src/test_invoice_search_ranking.rs +++ b/quicklendx-contracts/src/test_invoice_search_ranking.rs @@ -4,10 +4,7 @@ mod test_invoice_search_ranking { use crate::invoice_search::InvoiceSearch; use crate::storage::InvoiceStorage; - use crate::types::{ - Dispute, Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, - }; - use crate::QuickLendXContract; + use crate::types::{Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, Dispute}; use soroban_sdk::testutils::Address as _; use soroban_sdk::{Address, BytesN, Env, String, Vec}; @@ -17,11 +14,6 @@ mod test_invoice_search_ranking { env } - fn with_contract(env: &Env, f: impl FnOnce() -> T) -> T { - let contract_id = env.register(QuickLendXContract, ()); - env.as_contract(&contract_id, f) - } - fn create_test_invoice( env: &Env, business: &Address, @@ -101,8 +93,6 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x62; exact_id_bytes[2] = 0x63; let exact_id = BytesN::from_array(&env, &exact_id_bytes); - let exact_id_query = - "6162630000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -148,24 +138,29 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has 6162630000000000000000000000000000000000000000000000000000000000 in description", + "this has abc in description", None, None, 1000, ); // Invoice 3: No match - let invoice_other = - create_test_invoice(&env, &business, "unrelated search target", None, None, 1000); + let invoice_other = create_test_invoice( + &env, + &business, + "unrelated search target", + None, + None, + 1000, + ); + + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + InvoiceStorage::store_invoice(&env, &invoice_other); // Query "abc" (corresponds to exact_id hex string) - let query = String::from_str(&env, exact_id_query); - let results = with_contract(&env, || { - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - InvoiceStorage::store_invoice(&env, &invoice_other); - InvoiceSearch::search_invoices(&env, query).unwrap() - }); + let query = String::from_str(&env, "6162630000000000000000000000000000000000000000000000000000000000"); + let results = InvoiceSearch::search_invoices(&env, query).unwrap(); // Should return 2 results: exact match first, then partial match. Other should be filtered out. assert_eq!(results.len(), 2); @@ -190,13 +185,12 @@ mod test_invoice_search_ranking { let invoice_mid = create_test_invoice(&env, &business, "abc mid", None, None, 2000); let invoice_new = create_test_invoice(&env, &business, "abc new", None, None, 3000); + InvoiceStorage::store_invoice(&env, &invoice_old); + InvoiceStorage::store_invoice(&env, &invoice_mid); + InvoiceStorage::store_invoice(&env, &invoice_new); + let query = String::from_str(&env, "abc"); - let results = with_contract(&env, || { - InvoiceStorage::store_invoice(&env, &invoice_old); - InvoiceStorage::store_invoice(&env, &invoice_mid); - InvoiceStorage::store_invoice(&env, &invoice_new); - InvoiceSearch::search_invoices(&env, query).unwrap() - }); + let results = InvoiceSearch::search_invoices(&env, query).unwrap(); assert_eq!(results.len(), 3); @@ -222,8 +216,6 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x79; exact_id_bytes[2] = 0x7a; let exact_id = BytesN::from_array(&env, &exact_id_bytes); - let exact_id_query = - "78797a0000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -269,19 +261,18 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has 78797a0000000000000000000000000000000000000000000000000000000000 in description", + "this has 78797a in description", None, None, 5000, ); + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + // Query by the hex string of exact_id - let query = String::from_str(&env, exact_id_query); - let results = with_contract(&env, || { - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - InvoiceSearch::search_invoices(&env, query).unwrap() - }); + let query = String::from_str(&env, "78797a0000000000000000000000000000000000000000000000000000000000"); + let results = InvoiceSearch::search_invoices(&env, query).unwrap(); assert_eq!(results.len(), 2); // Exact ID must be first, despite having a lower created_at timestamp From 5056d141a2079b4b55e26d8db115b4255842dd92 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 15:36:21 +0800 Subject: [PATCH 5/6] test: keep stale coverage modules legacy-gated --- quicklendx-contracts/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quicklendx-contracts/src/lib.rs b/quicklendx-contracts/src/lib.rs index 022588bd..54715e6d 100644 --- a/quicklendx-contracts/src/lib.rs +++ b/quicklendx-contracts/src/lib.rs @@ -76,7 +76,7 @@ mod test_admin; mod test_admin_simple; #[cfg(all(test, feature = "legacy-tests"))] mod test_admin_standalone; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_admin_two_step; #[cfg(all(test, feature = "legacy-tests"))] mod test_audit; @@ -86,7 +86,7 @@ mod test_backup; mod test_backup_safety; #[cfg(all(test, feature = "legacy-tests"))] mod test_backup_restore_reindex; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_escrow_event_completeness; #[cfg(all(test, feature = "legacy-tests"))] mod test_bid_ttl; @@ -149,7 +149,7 @@ mod test_analytics_consistency; mod test_bid_ranking; #[cfg(all(test, feature = "legacy-tests"))] mod test_events; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_pause_reads_available; #[cfg(all(test, feature = "fuzz-tests"))] mod test_fuzz_invoice_metadata; @@ -168,11 +168,11 @@ mod test_investment_transitions; mod test_invoice_metadata; #[cfg(test)] mod test_invoice_search_ranking; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_rebuild_indexes; #[cfg(all(test, feature = "legacy-tests"))] mod test_max_invoices_per_business; -#[cfg(test)] +#[cfg(all(test, feature = "legacy-tests"))] mod test_category_breakdown; #[cfg(all(test, feature = "legacy-tests"))] mod test_diagnostics; From 3ed6acdbb08103d2d12a5ac18b7c3596bbe2edbd Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 19 Jun 2026 15:47:13 +0800 Subject: [PATCH 6/6] test: run invoice search ranking under contract context --- .../src/test_invoice_search_ranking.rs | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/quicklendx-contracts/src/test_invoice_search_ranking.rs b/quicklendx-contracts/src/test_invoice_search_ranking.rs index 32e4f556..27262650 100644 --- a/quicklendx-contracts/src/test_invoice_search_ranking.rs +++ b/quicklendx-contracts/src/test_invoice_search_ranking.rs @@ -4,7 +4,10 @@ mod test_invoice_search_ranking { use crate::invoice_search::InvoiceSearch; use crate::storage::InvoiceStorage; - use crate::types::{Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, Dispute}; + use crate::types::{ + Dispute, Invoice, InvoiceCategory, InvoiceStatus, SearchRank, SearchResult, + }; + use crate::QuickLendXContract; use soroban_sdk::testutils::Address as _; use soroban_sdk::{Address, BytesN, Env, String, Vec}; @@ -14,6 +17,11 @@ mod test_invoice_search_ranking { env } + fn with_contract(env: &Env, f: impl FnOnce() -> T) -> T { + let contract_id = env.register(QuickLendXContract, ()); + env.as_contract(&contract_id, f) + } + fn create_test_invoice( env: &Env, business: &Address, @@ -93,6 +101,7 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x62; exact_id_bytes[2] = 0x63; let exact_id = BytesN::from_array(&env, &exact_id_bytes); + let exact_id_query = "6162630000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -138,29 +147,24 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has abc in description", + "this has 6162630000000000000000000000000000000000000000000000000000000000 in description", None, None, 1000, ); // Invoice 3: No match - let invoice_other = create_test_invoice( - &env, - &business, - "unrelated search target", - None, - None, - 1000, - ); - - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - InvoiceStorage::store_invoice(&env, &invoice_other); + let invoice_other = + create_test_invoice(&env, &business, "unrelated search target", None, None, 1000); // Query "abc" (corresponds to exact_id hex string) - let query = String::from_str(&env, "6162630000000000000000000000000000000000000000000000000000000000"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let query = String::from_str(&env, exact_id_query); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + InvoiceStorage::store_invoice(&env, &invoice_other); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); // Should return 2 results: exact match first, then partial match. Other should be filtered out. assert_eq!(results.len(), 2); @@ -185,12 +189,13 @@ mod test_invoice_search_ranking { let invoice_mid = create_test_invoice(&env, &business, "abc mid", None, None, 2000); let invoice_new = create_test_invoice(&env, &business, "abc new", None, None, 3000); - InvoiceStorage::store_invoice(&env, &invoice_old); - InvoiceStorage::store_invoice(&env, &invoice_mid); - InvoiceStorage::store_invoice(&env, &invoice_new); - let query = String::from_str(&env, "abc"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_old); + InvoiceStorage::store_invoice(&env, &invoice_mid); + InvoiceStorage::store_invoice(&env, &invoice_new); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); assert_eq!(results.len(), 3); @@ -216,6 +221,7 @@ mod test_invoice_search_ranking { exact_id_bytes[1] = 0x79; exact_id_bytes[2] = 0x7a; let exact_id = BytesN::from_array(&env, &exact_id_bytes); + let exact_id_query = "78797a0000000000000000000000000000000000000000000000000000000000"; let dispute = Dispute { created_by: business.clone(), @@ -261,18 +267,19 @@ mod test_invoice_search_ranking { let invoice_partial = create_test_invoice( &env, &business, - "this has 78797a in description", + "this has 78797a0000000000000000000000000000000000000000000000000000000000 in description", None, None, 5000, ); - InvoiceStorage::store_invoice(&env, &invoice_exact); - InvoiceStorage::store_invoice(&env, &invoice_partial); - // Query by the hex string of exact_id - let query = String::from_str(&env, "78797a0000000000000000000000000000000000000000000000000000000000"); - let results = InvoiceSearch::search_invoices(&env, query).unwrap(); + let query = String::from_str(&env, exact_id_query); + let results = with_contract(&env, || { + InvoiceStorage::store_invoice(&env, &invoice_exact); + InvoiceStorage::store_invoice(&env, &invoice_partial); + InvoiceSearch::search_invoices(&env, query).unwrap() + }); assert_eq!(results.len(), 2); // Exact ID must be first, despite having a lower created_at timestamp