diff options
Diffstat (limited to 'src/test.rs')
| -rw-r--r-- | src/test.rs | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..15a7002 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,409 @@ +use crate::{bill::Bills, calc::calculate_from}; + +#[test] +fn test_no_zero_amount_transactions() { + let mut bills = Bills::default(); + + // Create some bills where some transaction amounts might be 0 + // A pays 30, split among A, B, C (10 each) + bills.add_bill("A", "Lunch", 30., vec!["A", "B", "C"]); + // B pays 30, split among A, B (15 each) + bills.add_bill("B", "Coffee", 30., vec!["A", "B"]); + + let result = calculate_from(bills); + assert!(result.is_ok(), "calculate should be success"); + let result = result.unwrap(); + + // Check if there are any transactions with amount 0 in the complete record (items) + let all_items = result.get_all_bills(); + for (payer, bills_list) in all_items { + for bill in bills_list { + assert_ne!( + bill.bill, 0.0, + "There should be no transactions with amount 0 in the complete record: {} to {} amount is 0", + payer, bill.payee + ); + } + } + + // Check if there are any transactions with amount 0 in the simplified result (final_result) + let final_result = result.get_final_result(); + for ((payer, payee), amount) in final_result { + assert_ne!( + *amount, 0.0, + "There should be no transactions with amount 0 in the simplified result: {} to {} amount is 0", + payer, payee + ); + } + + // Verify transaction count + // Should have: C->A (10), A->B (5) after netting + assert_eq!( + final_result.len(), + 2, + "There should be 2 non-zero transactions" + ); + assert!( + final_result.contains_key(&("C".into(), "A".into())), + "Should contain transaction C->A" + ); + assert!( + final_result.contains_key(&("A".into(), "B".into())), + "Should contain transaction A->B" + ); + + // Verify specific amounts + let c_to_a = final_result.get(&("C".into(), "A".into())).unwrap(); + assert_eq!(*c_to_a, 10.0, "C should pay A 10"); + + let a_to_b = final_result.get(&("A".into(), "B".into())).unwrap(); + assert_eq!(*a_to_b, 5.0, "A should pay B 5"); +} + +#[test] +fn test_zero_amount_edge_cases() { + let mut bills = Bills::default(); + + // Test perfectly balanced case: A and B prepay the same amount for each other + // A prepays 20 for A, B (10 each) + bills.add_bill("A", "Dinner", 20., vec!["A", "B"]); + // B prepays 20 for A, B (10 each) + bills.add_bill("B", "Movie", 20., vec!["A", "B"]); + + let result = calculate_from(bills); + assert!(result.is_ok(), "calculate should be success"); + let result = result.unwrap(); + + // Check complete record + let all_items = result.get_all_bills(); + for (payer, bills_list) in all_items { + for bill in bills_list { + assert_ne!( + bill.bill, 0.0, + "There should be no transactions with amount 0 in the complete record: {} to {} amount is 0", + payer, bill.payee + ); + } + } + + // Check simplified result - should be empty because all transactions cancel out + let final_result = result.get_final_result(); + assert_eq!( + final_result.len(), + 0, + "The simplified result should be empty because all transaction amounts are 0" + ); + + // Verify no transactions are included + assert!( + !final_result.contains_key(&("A".into(), "B".into())), + "Should not contain A->B zero amount transaction" + ); + assert!( + !final_result.contains_key(&("B".into(), "A".into())), + "Should not contain B->A zero amount transaction" + ); +} + +#[test] +fn test_items_count() { + let mut bills = Bills::default(); + + // Add 3 bill items + let id1 = bills.add_bill("A", "Lunch", 30., vec!["A", "B"]); + let id2 = bills.add_bill("B", "Coffee", 20., vec!["B", "C"]); + let id3 = bills.add_bill("C", "Snack", 15., vec!["A", "C"]); + + // Verify items count + assert_eq!(bills.get_all_items().len(), 3, "Should have 3 items"); + + // Verify each ID exists + assert!(bills.contains_item(&id1), "Item 1 should exist"); + assert!(bills.contains_item(&id2), "Item 2 should exist"); + assert!(bills.contains_item(&id3), "Item 3 should exist"); + + // Verify count after deleting one item + let removed = bills.delete_item(&id2); + assert!(removed.is_some(), "Should remove item 2"); + assert_eq!( + bills.get_all_items().len(), + 2, + "Should have 2 items after removal" + ); + + // Verify deleted item no longer exists + assert!( + !bills.contains_item(&id2), + "Item 2 should not exist after removal" + ); + + // Verify count after clearing + bills.clear_items(); + assert_eq!( + bills.get_all_items().len(), + 0, + "Should have 0 items after clear" + ); +} + +#[test] +fn test_result() { + let mut bills = Bills::default(); + + // Define data + bills.add_bill("A", "BBQ", 90., vec!["A", "B", "C"]); + bills.add_bill("B", "Water", 21., vec!["A", "B", "C"]); + + // Calculate + let result = calculate_from(bills); + + // Check result + assert!(result.is_ok(), "calculate should be success"); + + let result = result.unwrap(); + + // Verify split results + let c_to_a = result + .get_final_result_item("C".into(), "A".into()) + .expect("Item C to A should be exist"); + assert_eq!(c_to_a, 30.0, "C should pay A 30 for BBQ"); + + let c_to_b = result + .get_final_result_item("C".into(), "B".into()) + .expect("Item C to B should be exist"); + assert_eq!(c_to_b, 7.0, "C should pay B 7 for Water"); + + let b_to_a = result + .get_final_result_item("B".into(), "A".into()) + .expect("Item B to A should be exist"); + assert_eq!(b_to_a, 23.0, "B should pay A 23 (30 - 7)"); + + // Verify count + let final_result = result.get_final_result(); + assert_eq!(final_result.len(), 3, "Should have exactly 3 transactions"); +} + +#[test] +fn test_complex_bills() { + let mut bills = Bills::default(); + + // A prepays 50 for B and C, B and C should each pay A 25 + bills.add_bill("A", "Dinner", 50., vec!["B", "C"]); + + let result = calculate_from(bills); + assert!(result.is_ok(), "calculate should be success"); + let result = result.unwrap(); + + let b_to_a = result + .get_final_result_item("B".into(), "A".into()) + .expect("Item B to A should be exist"); + assert_eq!(b_to_a, 25.0, "B should pay A 25"); + + let c_to_a = result + .get_final_result_item("C".into(), "A".into()) + .expect("Item C to A should be exist"); + assert_eq!(c_to_a, 25.0, "C should pay A 25"); + + let final_result = result.get_final_result(); + assert_eq!(final_result.len(), 2, "Should have exactly 2 transactions"); +} + +#[test] +fn test_unrelated_bills() { + let mut bills = Bills::default(); + + // A prepays 30 split among A, B, C, each should pay A 10 + // B prepays 30 split among A, B, each should pay B 15 + // Final result: C only has transaction with A, not with B + bills.add_bill("A", "Lunch", 30., vec!["A", "B", "C"]); + bills.add_bill("B", "Coffee", 30., vec!["A", "B"]); + + let result = calculate_from(bills); + assert!(result.is_ok(), "calculate should be success"); + let result = result.unwrap(); + + // Verify C only has transaction with A, not with B + let c_to_a = result.get_final_result_item("C".into(), "A".into()); + assert!(c_to_a.is_some(), "C should pay A"); + assert_eq!(c_to_a.unwrap(), 10.0, "C should pay A 10"); + + let c_to_b = result.get_final_result_item("C".into(), "B".into()); + assert!(c_to_b.is_none(), "C should not have any transaction with B"); + + // Verify transaction between A and B + let a_to_b = result.get_final_result_item("A".into(), "B".into()); + assert!(a_to_b.is_some(), "A should pay B"); + assert_eq!(a_to_b.unwrap(), 5.0, "A should pay B 5 (15 - 10)"); + + // Verify total transaction count + let final_result = result.get_final_result(); + assert_eq!(final_result.len(), 2, "Should have exactly 2 transactions"); +} + +#[test] +fn test_duplicate_split_members() { + let mut bills = Bills::default(); + + // Duplicate members in split list, should return Error + bills.add_bill("Alice", "Lunch", 60., vec!["Bob", "Bob", "Charlie"]); + + let result = calculate_from(bills); + assert!( + result.is_err(), + "Should return error for duplicate split members" + ); +} + +#[test] +fn test_negative_paid_amount() { + let mut bills = Bills::default(); + + // Negative prepaid amount, should return Error + bills.add_bill("Alice", "Refund?", -30., vec!["Alice", "Bob"]); + + let result = calculate_from(bills); + assert!( + result.is_err(), + "Should return error for negative paid amount" + ); +} + +#[test] +fn test_rounding() { + let mut bills = Bills::default(); + + // Test rounding: 51.0333333333 => 51.00, 51.599999999999 => 52.00 + // 100 / 3 = 33.333..., each should pay 33.33, payer gets back 66.67 + bills.add_bill("Alice", "Concert", 100., vec!["Alice", "Bob", "Charlie"]); + + let result = calculate_from(bills); + assert!(result.is_ok(), "calculate should be success"); + let result = result.unwrap(); + + let bob_to_alice = result + .get_final_result_item("Bob".into(), "Alice".into()) + .expect("Item Bob to Alice should be exist"); + // 33.333... rounded to 2 decimal places => 33.33 + assert_eq!(bob_to_alice, 33.33, "Bob should pay Alice 33.33"); + + let charlie_to_alice = result + .get_final_result_item("Charlie".into(), "Alice".into()) + .expect("Item Charlie to Alice should be exist"); + assert_eq!(charlie_to_alice, 33.33, "Charlie should pay Alice 33.33"); + + // Another test: 51.599999999999 => 52.00 + let mut bills2 = Bills::default(); + bills2.add_bill("Bob", "Dinner", 51.6, vec!["Alice", "Bob"]); // 51.6 / 2 = 25.8 + + let result2 = calculate_from(bills2); + assert!(result2.is_ok(), "calculate should be success"); + let result2 = result2.unwrap(); + + let alice_to_bob = result2 + .get_final_result_item("Alice".into(), "Bob".into()) + .expect("Item Alice to Bob should be exist"); + // 25.8 rounded to 2 decimal places => 25.80 + assert_eq!(alice_to_bob, 25.8, "Alice should pay Bob 25.8"); +} + +#[test] +fn test_empty_bills() { + let bills = Bills::default(); + let result = calculate_from(bills); + assert!( + result.is_ok(), + "calculate should be success for empty bills" + ); + let result = result.unwrap(); + + assert_eq!( + result.get_all_bills().len(), + 0, + "Items should be empty for empty bills" + ); + assert_eq!( + result.get_final_result().len(), + 0, + "Final result should be empty for empty bills" + ); +} + +#[test] +fn test_single_person_bill() { + let mut bills = Bills::default(); + + // Single person bill: paying for oneself + bills.add_bill("Alice", "Personal", 50., vec!["Alice"]); + + let result = calculate_from(bills); + assert!( + result.is_ok(), + "calculate should be success for single person bill" + ); + let result = result.unwrap(); + + // Single person bill should not generate any transactions + assert_eq!( + result.get_final_result().len(), + 0, + "Should have no transactions for single person bill" + ); +} + +#[test] +fn test_split_not_include_payer() { + let mut bills = Bills::default(); + + // Payer not included in split list + bills.add_bill("Alice", "Gift", 100., vec!["Bob", "Charlie"]); + + let result = calculate_from(bills); + assert!( + result.is_ok(), + "calculate should be success when payer not in split" + ); + let result = result.unwrap(); + + // Bob and Charlie should each pay Alice 50 + let bob_to_alice = result + .get_final_result_item("Bob".into(), "Alice".into()) + .expect("Bob should pay Alice"); + assert_eq!(bob_to_alice, 50.0, "Bob should pay Alice 50"); + + let charlie_to_alice = result + .get_final_result_item("Charlie".into(), "Alice".into()) + .expect("Charlie should pay Alice"); + assert_eq!(charlie_to_alice, 50.0, "Charlie should pay Alice 50"); +} + +#[test] +fn test_large_number_of_people() { + let mut bills = Bills::default(); + + // Test large group scenario + let people = vec!["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]; + bills.add_bill("A", "Group Dinner", 1000., people.clone()); + + let result = calculate_from(bills); + assert!( + result.is_ok(), + "calculate should be success for large group" + ); + let result = result.unwrap(); + + // Each person should pay A 100 (1000/10) + for person in &people { + if *person != "A" { + let amount = result + .get_final_result_item(person.to_string().into(), "A".into()) + .expect(&format!("{} should pay A", person)); + assert_eq!(amount, 100.0, "{} should pay A 100", person); + } + } + + assert_eq!( + result.get_final_result().len(), + 9, + "Should have 9 transactions" + ); +} |
