Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions contracts/chainsetttle/src/test_new_milestone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,178 @@

extern crate std;

use super::*;
use crate::test_common::{build_milestones, default_options, setup, single_buyer_vec};
use soroban_sdk::{testutils::Ledger as _, vec, Env, String, Symbol};

fn sid(env: &Env, s: &str) -> String {
String::from_str(env, s)
}

fn ipfs(env: &Env) -> Symbol {
Symbol::new(env, "ipfs")
}

// ============================================================
// Issue #42 — Minimum shipment value
// ============================================================

#[test]
fn test_min_shipment_value_default_zero_allows_any_amount() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
// Default min = 0 (disabled) — small shipment must succeed
client.create_shipment(
&sid(&t.env, "min-default"),
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1,
&vec![
&t.env,
Milestone {
name: sid(&t.env, "M"),
payment_percent: 100,
proof_hash: sid(&t.env, ""),
status: MilestoneStatus::Pending,
release_after_ledger: 0,
proof_submitted_ledger: None,
dispute_opened_ledger: None,
deadline_ledger: 0,
penalty_bps_per_ledger: 0,
},
],
&default_options(&t.env),
);
assert_eq!(client.get_min_shipment_value(), 0);
}

#[test]
#[should_panic(expected = "MinShipmentValueNotMet")]
fn test_min_shipment_value_below_floor_panics() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
client.set_min_shipment_value(&t.buyer, &1_000);
// total_amount = 500 < 1_000 → must panic
client.create_shipment(
&sid(&t.env, "below-floor"),
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&500,
&vec![
&t.env,
Milestone {
name: sid(&t.env, "M"),
payment_percent: 100,
proof_hash: sid(&t.env, ""),
status: MilestoneStatus::Pending,
release_after_ledger: 0,
proof_submitted_ledger: None,
dispute_opened_ledger: None,
deadline_ledger: 0,
penalty_bps_per_ledger: 0,
},
],
&default_options(&t.env),
);
}

// ============================================================
// Issue #299 — Shipment-level fee override
// ============================================================

#[test]
fn test_shipment_fee_override_cleared_reverts_to_global() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "fee-clear");
client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);
client.set_shipment_fee_override(&t.buyer, &ship_id, &50u32);
client.clear_shipment_fee_override(&t.buyer, &ship_id);
assert_eq!(client.get_shipment_fee_override(&ship_id), None);
}

// ============================================================
// Issue #300 — Long-hold escrow rebate
// ============================================================

#[test]
fn test_long_hold_rebate_getter_after_set() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
client.set_long_hold_rebate(&t.buyer, &1000u32, &500u32);
assert_eq!(client.get_long_hold_rebate(), (1000u32, 500u32));
}

// ============================================================
// Issue #298 — Governance timelock
// ============================================================

#[test]
#[should_panic(expected = "TimelockNotExpired")]
fn test_execute_param_change_before_delay_panics() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
client.set_timelock_duration(&t.buyer, &100u32);
let param = Symbol::new(&t.env, "max_shipment_value");
client.propose_param_change(&t.buyer, &param, &500_000);
// Do NOT advance ledger — must panic
client.execute_param_change(&t.buyer, &param);
}

#[test]
#[should_panic(expected = "NoPendingParamChange")]
fn test_cancel_param_change_removes_pending() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
client.set_timelock_duration(&t.buyer, &50u32);
let param = Symbol::new(&t.env, "max_shipment_value");
client.propose_param_change(&t.buyer, &param, &1_000_000);
client.cancel_param_change(&t.buyer, &param);
// Advance past timelock and try to execute — must panic (no pending change)
t.env.ledger().with_mut(|l| l.sequence_number += 51);
client.execute_param_change(&t.buyer, &param);
}

#[test]
fn test_timelock_zero_executes_immediately() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
// Default timelock = 0 → effective_ledger = current → can execute immediately
let param = Symbol::new(&t.env, "max_shipment_value");
client.propose_param_change(&t.buyer, &param, &777_777);
client.execute_param_change(&t.buyer, &param);
assert_eq!(client.get_max_shipment_value(), 777_777);
}

#[test]
fn test_circuit_breaker_limit_param_change() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let param = Symbol::new(&t.env, "circuit_breaker_limit");
client.propose_param_change(&t.buyer, &param, &5_000_000);
client.execute_param_change(&t.buyer, &param);
// No panic = success; circuit breaker limit updated
}
#![cfg(test)]

extern crate std;

use super::*;
use crate::test_common::{create_standard_shipment, setup, submit_proof_for_milestone, TestSetup};
use soroban_sdk::testutils::{Address as _, Events as _};
Expand Down
212 changes: 212 additions & 0 deletions contracts/chainsetttle/src/test_observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,218 @@

extern crate std;

use super::*;
use crate::test_common::{build_milestones, default_options, setup, single_buyer_vec};
use soroban_sdk::{vec, BytesN, Env, String, Symbol};

fn sid(env: &Env, id: &str) -> String {
String::from_str(env, id)
}

fn ipfs(env: &Env) -> Symbol {
Symbol::new(env, "ipfs")
}

// ============================================================
// #113 – Fee tier tests
// ============================================================

#[test]
fn test_fee_tier_no_tiers_uses_default_fee() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
client.set_fee_config(&t.buyer, &100u32, &t.treasury);
// No tiers: get_fee_tier returns FeeConfig.fee_bps
assert_eq!(client.get_fee_tier(&t.buyer), 100);
}

// ============================================================
// #112 – Invoice hash tests
// ============================================================

#[test]
fn test_no_invoice_hash_returns_none() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "nohash");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);

assert!(client.get_invoice_hash(&ship_id, &0u32).is_none());
}

#[test]
#[should_panic(expected = "invoice hash already set and is immutable")]
fn test_invoice_hash_immutable_after_first_submission() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "invimm");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);
client.submit_proof(
&t.supplier,
&ship_id,
&0u32,
&sid(&t.env, "h0"),
&ipfs(&t.env),
);
let hash = BytesN::from_array(&t.env, &[0xAAu8; 32]);
client.attach_invoice_hash(&t.supplier, &ship_id, &0u32, &hash);
// Second attach must panic
client.attach_invoice_hash(&t.supplier, &ship_id, &0u32, &hash);
}

// ============================================================
// #111 – Amendment log tests
// ============================================================

// Helper: milestones where index 0 can validly change percent (100% single milestone)
fn single_milestone(env: &Env, name: &str) -> soroban_sdk::Vec<Milestone> {
vec![
env,
Milestone {
name: String::from_str(env, name),
payment_percent: 100,
proof_hash: String::from_str(env, ""),
status: MilestoneStatus::Pending,
release_after_ledger: 0,
proof_submitted_ledger: None,
dispute_opened_ledger: None,
deadline_ledger: 0,
penalty_bps_per_ledger: 0,
},
]
}

#[test]
fn test_amendment_log_empty_on_unilateral_proposal() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "amlog2");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);

// Only buyer proposes → no acceptance
client.propose_amendment(&t.buyer, &ship_id, &0u32, &25u32, &sid(&t.env, "V2"));
assert_eq!(client.get_amendment_log(&ship_id, &0u32).len(), 0);
}

// ============================================================
// #110 – Extension request tests
// ============================================================

#[test]
fn test_extension_request_count_includes_approved_requests() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "extapproved");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);
client.set_max_extension_requests(&t.buyer, &2u32);

client.request_extension(&t.supplier, &ship_id, &0u32, &100u32);
client.approve_extension(&t.buyer, &ship_id, &0u32);
client.request_extension(&t.supplier, &ship_id, &0u32, &200u32);
client.approve_extension(&t.buyer, &ship_id, &0u32);

assert!(client.get_milestone_deadline(&ship_id, &0u32) > 0);
}

#[test]
#[should_panic(expected = "max extension requests reached")]
fn test_extension_request_over_limit_rejected() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "extlimit");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);
client.set_max_extension_requests(&t.buyer, &1u32);

client.request_extension(&t.supplier, &ship_id, &0u32, &100u32);
client.deny_extension(&t.buyer, &ship_id, &0u32);

// second request should be rejected because the milestone has already reached cap
client.request_extension(&t.supplier, &ship_id, &0u32, &200u32);
}

#[test]
#[should_panic(expected = "extension request already pending")]
fn test_double_extension_request_rejected() {
let t = setup();
let client = ChainSettleContractClient::new(&t.env, &t.contract_id);
let ship_id = sid(&t.env, "extdbl");

client.create_shipment(
&ship_id,
&single_buyer_vec(&t.env, &t.buyer),
&t.supplier,
&t.logistics,
&t.arbiter,
&t.token_id,
&1_000_000,
&build_milestones(&t.env),
&default_options(&t.env),
);

client.request_extension(&t.supplier, &ship_id, &0u32, &100u32);
// Second request while first is pending must panic
client.request_extension(&t.supplier, &ship_id, &0u32, &200u32);
}

#![cfg(test)]

extern crate std;

use super::*;
use soroban_sdk::{testutils::Address as _, Address, String, vec};
use crate::test_common::*;
Expand Down
Loading
Loading