1#[cfg(test)]
2mod tests {
3 use crate::{
4 Region, TaxDatabase, TaxScenario, TaxType, TradeAgreementOverride, TransactionType, VatRate,
5 };
6 use rust_decimal_macros::dec;
7
8 fn setup() -> TaxDatabase {
9 let _ = env_logger::builder()
10 .is_test(true)
11 .filter_level(log::LevelFilter::Debug) .try_init();
13 TaxDatabase::from_files("vat_rates.json", "trade_agreements.json")
14 .expect("Tax database should load")
15 }
16
17 #[test]
18 fn test_german_vat_calculation() {
19 let db = setup();
20 let scenario = TaxScenario::new(
21 Region::new("DE".to_string(), None).expect("Valid German region"),
22 Region::new("DE".to_string(), None).expect("Valid German region"),
23 TransactionType::B2C,
24 );
25 let tax = scenario
26 .calculate_tax(100.0, &db)
27 .expect("Tax calculation should succeed");
28 assert_eq!(tax, 19.0); let rates = scenario
31 .get_rates(100.0, &db)
32 .expect("Rates should be found");
33 assert_eq!(rates.len(), 1);
34 assert_eq!(rates[0].rate, 0.19);
35 assert_eq!(rates[0].tax_type, TaxType::VAT(VatRate::Standard));
36 assert!(!rates[0].compound);
37 }
38
39 #[test]
40 fn test_canadian_gst_bc_pst_below_threshold() {
41 let db = setup();
42 let scenario = TaxScenario::new(
43 Region::new("CA".to_string(), Some("CA-BC".to_string()))
44 .expect("Valid Canadian BC region"),
45 Region::new("CA".to_string(), Some("CA-BC".to_string()))
46 .expect("Valid Canadian BC region"),
47 TransactionType::B2C,
48 );
49
50 let tax = scenario
51 .calculate_tax(100.0, &db)
52 .expect("Tax calculation should succeed");
53 assert_eq!(tax, 0.0); }
55
56 #[test]
57 fn test_canadian_gst_bc_pst_ignore_threshold() {
58 let db = setup();
59 let mut scenario = TaxScenario::new(
60 Region::new("CA".to_string(), Some("CA-BC".to_string()))
61 .expect("Valid Canadian BC region"),
62 Region::new("CA".to_string(), Some("CA-BC".to_string()))
63 .expect("Valid Canadian BC region"),
64 TransactionType::B2C,
65 );
66 scenario.ignore_threshold = true;
67
68 let tax = scenario
69 .calculate_tax(100.0, &db)
70 .expect("Tax calculation should succeed");
71 assert_eq!(tax, 12.35); }
73
74 #[test]
75 fn test_canadian_gst_bc_pst_above_threshold() {
76 let db = setup();
77 let scenario = TaxScenario::new(
78 Region::new("CA".to_string(), Some("CA-BC".to_string()))
79 .expect("Valid Canadian BC region"),
80 Region::new("CA".to_string(), Some("CA-BC".to_string()))
81 .expect("Valid Canadian BC region"),
82 TransactionType::B2C,
83 );
84
85 let tax = scenario
86 .calculate_tax(100000.0, &db)
87 .expect("Tax calculation should succeed");
88 assert_eq!(tax, 12350.0); }
90
91 #[test]
92 fn test_eu_cross_border_b2b() {
93 let db = setup();
94 let scenario = TaxScenario::new(
95 Region::new("DE".to_string(), None).expect("Valid German region"),
96 Region::new("FR".to_string(), None).expect("Valid French region"),
97 TransactionType::B2B,
98 );
99
100 let tax = scenario
101 .calculate_tax(100.0, &db)
102 .expect("Tax calculation should succeed");
103 assert_eq!(tax, 0.0); }
105
106 #[test]
107 fn test_eu_cross_border_b2c_digital() {
108 let db = setup();
109 let mut scenario = TaxScenario::new(
110 Region::new("DE".to_string(), None).expect("Valid German region"),
111 Region::new("FR".to_string(), None).expect("Valid French region"),
112 TransactionType::B2B,
113 );
114 scenario.is_digital_product_or_service = true;
115
116 let tax = scenario
117 .calculate_tax(100.0, &db)
118 .expect("Tax calculation should succeed");
119 assert_eq!(tax, 0.0); }
121
122 #[test]
123 fn test_eu_cross_border_b2c() {
124 let db = setup();
125 let scenario = TaxScenario::new(
126 Region::new("DE".to_string(), None).expect("Valid German region"),
127 Region::new("FR".to_string(), None).expect("Valid French region"),
128 TransactionType::B2C,
129 );
130
131 let tax = scenario
132 .calculate_tax(100.0, &db)
133 .expect("Tax calculation should succeed");
134 assert_eq!(tax, 19.0); }
136
137 #[test]
138 fn test_french_reduced_vat() {
139 let db = setup();
140 let mut scenario = TaxScenario::new(
141 Region::new("FR".to_string(), None).expect("Valid French region"),
142 Region::new("FR".to_string(), None).expect("Valid French region"),
143 TransactionType::B2C,
144 );
145 scenario.vat_rate = Some(VatRate::ReducedAlt);
146
147 let tax = scenario
148 .calculate_tax(100.0, &db)
149 .expect("Tax calculation should succeed");
150 assert_eq!(tax, 5.5); }
152
153 #[test]
154 fn test_german_domestic_b2b() {
155 let db = setup();
156 let scenario = TaxScenario::new(
157 Region::new("DE".to_string(), None).expect("Valid German region"),
158 Region::new("DE".to_string(), None).expect("Valid German region"),
159 TransactionType::B2B,
160 );
161
162 let tax = scenario
163 .calculate_tax(100.0, &db)
164 .expect("Tax calculation should succeed");
165 assert_eq!(tax, 19.0); }
167
168 #[test]
169 fn test_germany_thailand_b2b() {
170 let db = setup();
171 let scenario = TaxScenario::new(
172 Region::new("DE".to_string(), None).expect("Valid German region"),
173 Region::new("TH".to_string(), None).expect("Valid Thai region"),
174 TransactionType::B2B,
175 );
176
177 let tax = scenario
178 .calculate_tax(100.0, &db)
179 .expect("Tax calculation should succeed");
180 assert_eq!(tax, 0.0); }
182
183 #[test]
184 fn test_germany_thailand_b2c() {
185 let db = setup();
186 let scenario = TaxScenario::new(
187 Region::new("DE".to_string(), None).expect("Valid German region"),
188 Region::new("TH".to_string(), None).expect("Valid Thai region"),
189 TransactionType::B2C,
190 );
191
192 let tax = scenario
193 .calculate_tax(100.0, &db)
194 .expect("Tax calculation should succeed");
195 assert_eq!(tax, 0.0); }
197
198 #[test]
199 fn test_us_interstate_b2c_below_threshold() {
200 let db = setup();
201 let scenario = TaxScenario::new(
202 Region::new("US".to_string(), Some("US-CA".to_string())).expect("Valid US-CA region"),
203 Region::new("US".to_string(), Some("US-WA".to_string())).expect("Valid US-WA region"),
204 TransactionType::B2C,
205 );
206
207 let tax = scenario
208 .calculate_tax(100.0, &db)
209 .expect("Tax calculation should succeed");
210 assert_eq!(tax, 0.0); }
212
213 #[test]
214 fn test_us_interstate_b2c_ignore_threshold() {
215 let db = setup();
216 let mut scenario = TaxScenario::new(
217 Region::new("US".to_string(), Some("US-CA".to_string())).expect("Valid US-CA region"),
218 Region::new("US".to_string(), Some("US-WA".to_string())).expect("Valid US-WA region"),
219 TransactionType::B2C,
220 );
221 scenario.ignore_threshold = true;
222
223 let tax = scenario
224 .calculate_tax(100.0, &db)
225 .expect("Tax calculation should succeed");
226 assert_eq!(tax, 6.5); }
228
229 #[test]
230 fn test_us_interstate_b2c_above_threshold() {
231 let db = setup();
232 let scenario = TaxScenario::new(
233 Region::new("US".to_string(), Some("US-CA".to_string())).expect("Valid US-CA region"),
234 Region::new("US".to_string(), Some("US-WA".to_string())).expect("Valid US-WA region"),
235 TransactionType::B2C,
236 );
237
238 let tax = scenario
239 .calculate_tax(100000.0, &db)
240 .expect("Tax calculation should succeed");
241 assert_eq!(tax, 6500.0); }
243
244 #[test]
245 fn test_us_interstate_b2b() {
246 let db = setup();
247 let mut scenario = TaxScenario::new(
248 Region::new("US".to_string(), Some("US-TX".to_string())).expect("Valid US-TX region"),
249 Region::new("US".to_string(), Some("US-WA".to_string())).expect("Valid US-WA region"),
250 TransactionType::B2B,
251 );
252 scenario.has_resale_certificate = true;
253
254 let tax = scenario
255 .calculate_tax(100.0, &db)
256 .expect("Tax calculation should succeed");
257 assert_eq!(tax, 0.0);
258 }
259
260 #[test]
261 fn test_us_interstate_b2b_reseller() {
262 let db = setup();
263 let mut scenario = TaxScenario::new(
264 Region::new("US".to_string(), Some("US-WA".to_string())).expect("Valid US-WA region"),
265 Region::new("US".to_string(), Some("US-TX".to_string())).expect("Valid US-TX region"),
266 TransactionType::B2B,
267 );
268 scenario.has_resale_certificate = true;
269
270 let tax = scenario
271 .calculate_tax(100.0, &db)
272 .expect("Tax calculation should succeed");
273 assert_eq!(tax, 0.0);
274 }
275
276 #[test]
277 fn test_gcc_cross_border_b2b() {
278 let db = setup();
279 let scenario = TaxScenario::new(
280 Region::new("AE".to_string(), None).expect("Valid UAE region"),
281 Region::new("QA".to_string(), None).expect("Valid Qatar region"),
282 TransactionType::B2B,
283 );
284
285 let tax = scenario
286 .calculate_tax(100.0, &db)
287 .expect("Tax calculation should succeed");
288 assert_eq!(tax, 0.0); }
290
291 #[test]
292 fn test_gcc_cross_border_b2c() {
293 let db = setup();
294 let scenario = TaxScenario::new(
295 Region::new("AE".to_string(), None).expect("Valid UAE region"),
296 Region::new("QA".to_string(), None).expect("Valid Qatar region"),
297 TransactionType::B2C,
298 );
299
300 let tax = scenario
301 .calculate_tax(100.0, &db)
302 .expect("Tax calculation should succeed");
303 assert_eq!(tax, 5.0); }
305
306 #[test]
307 fn test_gcc_cross_border_b2c_manual() {
308 let db = setup();
309 let scenario = TaxScenario {
310 source_region: Region::new("AE".to_string(), None).expect("Valid UAE region"),
311 destination_region: Region::new("QA".to_string(), None).expect("Valid Qatar region"),
312 transaction_type: TransactionType::B2C,
313 trade_agreement_override: None,
314 is_digital_product_or_service: false,
315 has_resale_certificate: false,
316 ignore_threshold: false,
317 vat_rate: None,
318 };
319
320 let tax = scenario
321 .calculate_tax(100.0, &db)
322 .expect("Tax calculation should succeed");
323 assert_eq!(tax, 5.0); }
325
326 #[test]
327 fn test_gcc_cross_border_b2c_manual_no_agreement() {
328 let db = setup();
329 let scenario = TaxScenario {
330 source_region: Region::new("AE".to_string(), None).expect("Valid UAE region"),
331 destination_region: Region::new("QA".to_string(), None).expect("Valid Qatar region"),
332 transaction_type: TransactionType::B2C,
333 trade_agreement_override: Some(TradeAgreementOverride::NoAgreement),
334 is_digital_product_or_service: false,
335 has_resale_certificate: false,
336 ignore_threshold: false,
337 vat_rate: None,
338 };
339
340 let tax = scenario
341 .calculate_tax(100.0, &db)
342 .expect("Tax calculation should succeed");
343 assert_eq!(tax, 0.0); }
345
346 #[test]
347 fn test_canadian_quebec_gst_qst() {
348 let db = setup();
349 let scenario = TaxScenario::new(
350 Region::new("CA".to_string(), Some("CA-QC".to_string()))
351 .expect("Valid Canadian QC region"),
352 Region::new("CA".to_string(), Some("CA-QC".to_string()))
353 .expect("Valid Canadian QC region"),
354 TransactionType::B2C,
355 );
356
357 let rates = scenario
358 .get_rates(100.0, &db)
359 .expect("Rates should be found");
360 assert_eq!(rates.len(), 2); let gst_rate = rates
363 .iter()
364 .find(|r| matches!(r.tax_type, TaxType::GST))
365 .expect("Should have GST");
366 assert_eq!(gst_rate.rate, 0.05); let qst_rate = rates
369 .iter()
370 .find(|r| matches!(r.tax_type, TaxType::QST))
371 .expect("Should have QST");
372 assert_eq!(qst_rate.rate, 0.09975); assert!(qst_rate.compound); }
375
376 #[test]
377 fn test_canadian_nova_scotia_hst() {
378 let db = setup();
379 let scenario = TaxScenario::new(
380 Region::new("CA".to_string(), Some("CA-NS".to_string()))
381 .expect("Valid Canadian NS region"),
382 Region::new("CA".to_string(), Some("CA-NS".to_string()))
383 .expect("Valid Canadian NS region"),
384 TransactionType::B2C,
385 );
386
387 let rates = scenario
388 .get_rates(100.0, &db)
389 .expect("Rates should be found");
390 assert_eq!(rates.len(), 1); assert_eq!(rates[0].tax_type, TaxType::HST);
392 assert_eq!(rates[0].rate, 0.10); assert!(!rates[0].compound); }
395
396 #[test]
397 fn test_eu_zero_rate() {
398 let db = setup();
399 let mut scenario = TaxScenario::new(
400 Region::new("IE".to_string(), None).expect("Valid Irish region"),
401 Region::new("IE".to_string(), None).expect("Valid Irish region"),
402 TransactionType::B2C,
403 );
404 scenario.vat_rate = Some(VatRate::Zero);
405
406 let tax = scenario
407 .calculate_tax(100.0, &db)
408 .expect("Tax calculation should succeed");
409 assert_eq!(tax, 0.0); }
411
412 #[test]
413 fn test_multiple_tax_rates() {
414 let db = setup();
415 let scenario = TaxScenario::new(
416 Region::new("CA".to_string(), Some("CA-BC".to_string()))
417 .expect("Valid Canadian BC region"),
418 Region::new("CA".to_string(), Some("CA-BC".to_string()))
419 .expect("Valid Canadian BC region"),
420 TransactionType::B2C,
421 );
422
423 let rates = scenario
424 .get_rates(100000.0, &db)
425 .expect("Rates should be found");
426 assert_eq!(rates.len(), 2); assert!(rates.iter().any(|r| matches!(r.tax_type, TaxType::GST)));
428 assert!(rates.iter().any(|r| matches!(r.tax_type, TaxType::PST)));
429 }
430
431 #[test]
432 fn test_reverse_charge_vat() {
433 let db = setup();
434 let mut scenario = TaxScenario::new(
435 Region::new("DE".to_string(), None).expect("Valid German region"),
436 Region::new("FR".to_string(), None).expect("Valid French region"),
437 TransactionType::B2B,
438 );
439 scenario.vat_rate = Some(VatRate::ReverseCharge);
440
441 let rates = scenario
442 .get_rates(100.0, &db)
443 .expect("Rates should be found");
444 assert_eq!(rates.len(), 1);
445 assert_eq!(rates[0].rate, 0.0);
446 assert!(matches!(
447 rates[0].tax_type,
448 TaxType::VAT(VatRate::ReverseCharge)
449 ));
450 }
451
452 #[test]
453 fn test_us_state_no_sales_tax() {
454 let db = setup();
455 let scenario = TaxScenario::new(
456 Region::new("US".to_string(), Some("US-OR".to_string())).expect("Valid US-OR region"),
457 Region::new("US".to_string(), Some("US-OR".to_string())).expect("Valid US-OR region"),
458 TransactionType::B2C,
459 );
460
461 let tax = scenario
462 .calculate_tax(100.0, &db)
463 .expect("Tax calculation should succeed");
464 assert_eq!(tax, 0.0); }
466
467 #[test]
468 fn test_us_states_get_rates() {
469 let db = setup();
470 let mut scenario = TaxScenario::new(
471 Region::new("US".to_string(), Some("US-AS".to_string())).expect("Valid US-AK region"),
472 Region::new("US".to_string(), Some("US-CA".to_string())).expect("Valid US-CA region"),
473 TransactionType::B2C,
474 );
475 scenario.ignore_threshold = true;
476
477 let rates = scenario.get_rates(1.0, &db).expect("Rates should be found");
478 assert_eq!(rates.len(), 1);
479 assert_eq!(rates[0].rate, 0.0825); }
481
482 #[test]
483 fn test_specific_trade_agreement() {
484 let mut scenario = TaxScenario::new(
486 Region::new("DE".to_string(), None).expect("Valid German region"),
487 Region::new("FR".to_string(), None).expect("Valid French region"),
488 TransactionType::B2C,
489 );
490 scenario.trade_agreement_override =
491 Some(TradeAgreementOverride::UseAgreement("EU".to_string()));
492
493 }
496
497 #[test]
498 fn test_exempt_vat_rate() {
499 let db = setup();
500 let mut scenario = TaxScenario::new(
501 Region::new("GB".to_string(), None).expect("Valid UK region"),
502 Region::new("GB".to_string(), None).expect("Valid UK region"),
503 TransactionType::B2C,
504 );
505 scenario.vat_rate = Some(VatRate::Exempt);
506
507 let rates = scenario
508 .get_rates(100.0, &db)
509 .expect("Rates should be found");
510 assert_eq!(rates.len(), 1);
511 assert_eq!(rates[0].rate, 0.0);
512 assert!(matches!(rates[0].tax_type, TaxType::VAT(VatRate::Exempt)));
513 }
514
515 #[test]
516 fn test_decimal_german_vat_calculation() {
517 let db = setup();
518 let scenario = TaxScenario::new(
519 Region::new("DE".to_string(), None).expect("Valid German region"),
520 Region::new("DE".to_string(), None).expect("Valid German region"),
521 TransactionType::B2C,
522 );
523
524 let tax = scenario
525 .calculate_tax_decimal(dec!(100.00), &db)
526 .expect("Tax calculation should succeed");
527 assert_eq!(tax, dec!(19.00)); }
529
530 #[test]
531 fn test_decimal_multiple_compound_calculations() {
532 let db = setup();
533 let scenario = TaxScenario::new(
534 Region::new("CA".to_string(), Some("CA-QC".to_string()))
535 .expect("Valid Canadian QC region"),
536 Region::new("CA".to_string(), Some("CA-QC".to_string()))
537 .expect("Valid Canadian QC region"),
538 TransactionType::B2C,
539 );
540
541 let amount = dec!(7999999.99);
542 let decimal_tax = scenario
543 .calculate_tax_decimal(amount, &db)
544 .expect("Decimal tax calculation should succeed");
545 let float_tax = scenario
546 .calculate_tax(7999999.99, &db)
547 .expect("Float tax calculation should succeed");
548
549 assert_eq!(decimal_tax, dec!(1237899.998452625));
550 assert_eq!(float_tax, 1237900.0); }
552
553 #[test]
554 fn load_included_db() {
555 let _ = TaxDatabase::new();
556 }
557}