#[cfg(feature = "postgres")]
use rust_decimal_macros::dec;
#[cfg(feature = "postgres")]
use stateset_core::{
CommerceError, CreateCustomer, CreateOrder, CreateOrderItem, CreateProduct, CreateReturn,
CreateReturnItem, ItemCondition, Order, ReturnFilter, ReturnReason,
};
#[cfg(feature = "postgres")]
use stateset_db::PostgresDatabase;
#[cfg(feature = "postgres")]
use std::env;
#[cfg(feature = "postgres")]
use uuid::Uuid;
#[cfg(feature = "postgres")]
fn postgres_url() -> Option<String> {
env::var("POSTGRES_URL").ok().or_else(|| env::var("DATABASE_URL").ok())
}
#[cfg(feature = "postgres")]
async fn order_with_item(
db: &PostgresDatabase,
quantity: i32,
unit_price: rust_decimal::Decimal,
) -> Order {
let unique = Uuid::new_v4().to_string();
let sku = format!("SKU-{}", unique.replace('-', ""));
let customer = db
.customers()
.create_async(CreateCustomer {
email: format!("ret-{unique}@example.com"),
first_name: "Ret".into(),
last_name: "Urn".into(),
phone: None,
accepts_marketing: Some(false),
tags: None,
metadata: None,
})
.await
.expect("create customer");
let product = db
.products()
.create_async(CreateProduct {
name: format!("Widget {unique}"),
slug: Some(format!("widget-{unique}")),
description: None,
product_type: None,
attributes: None,
seo: None,
variants: None,
})
.await
.expect("create product");
db.orders()
.create_async(CreateOrder {
customer_id: customer.id,
items: vec![CreateOrderItem {
product_id: product.id,
variant_id: None,
sku,
name: "Widget".into(),
quantity,
unit_price,
discount: None,
tax_amount: None,
}],
..Default::default()
})
.await
.expect("create order")
}
#[cfg(feature = "postgres")]
#[tokio::test]
async fn postgres_return_rejects_more_than_ordered_quantity() {
let url = match postgres_url() {
Some(url) => url,
None => {
eprintln!("POSTGRES_URL/DATABASE_URL not set; skipping");
return;
}
};
let db = PostgresDatabase::connect(&url).await.expect("connect + migrate");
let order = order_with_item(&db, 2, dec!(9.99)).await;
let order_item_id = order.items[0].id;
let err = db
.returns()
.create_async(CreateReturn {
order_id: order.id,
reason: ReturnReason::Defective,
reason_details: None,
idempotency_key: None,
items: vec![CreateReturnItem {
order_item_id,
quantity: 5,
condition: Some(ItemCondition::Defective),
}],
notes: None,
})
.await
.expect_err("returning 5 of a 2-unit item must be rejected");
assert!(matches!(err, CommerceError::ValidationError(_)), "got {err:?}");
let existing = db
.returns()
.list_async(ReturnFilter { order_id: Some(order.id), ..Default::default() })
.await
.expect("list returns");
assert!(existing.is_empty(), "rejected over-return left a partial return: {existing:?}");
}
#[cfg(feature = "postgres")]
#[tokio::test]
async fn postgres_return_rejects_cumulative_over_return() {
let url = match postgres_url() {
Some(url) => url,
None => {
eprintln!("POSTGRES_URL/DATABASE_URL not set; skipping");
return;
}
};
let db = PostgresDatabase::connect(&url).await.expect("connect + migrate");
let order = order_with_item(&db, 2, dec!(9.99)).await;
let order_item_id = order.items[0].id;
db.returns()
.create_async(CreateReturn {
order_id: order.id,
reason: ReturnReason::ChangedMind,
reason_details: None,
idempotency_key: None,
items: vec![CreateReturnItem { order_item_id, quantity: 2, condition: None }],
notes: None,
})
.await
.expect("returning the full ordered quantity is allowed");
let err = db
.returns()
.create_async(CreateReturn {
order_id: order.id,
reason: ReturnReason::ChangedMind,
reason_details: None,
idempotency_key: None,
items: vec![CreateReturnItem { order_item_id, quantity: 1, condition: None }],
notes: None,
})
.await
.expect_err("cumulative over-return must be rejected");
assert!(matches!(err, CommerceError::ValidationError(_)), "got {err:?}");
}
#[cfg(feature = "postgres")]
#[tokio::test]
async fn postgres_return_rejects_item_from_another_order() {
let url = match postgres_url() {
Some(url) => url,
None => {
eprintln!("POSTGRES_URL/DATABASE_URL not set; skipping");
return;
}
};
let db = PostgresDatabase::connect(&url).await.expect("connect + migrate");
let order_a = order_with_item(&db, 2, dec!(9.99)).await;
let order_b = order_with_item(&db, 2, dec!(19.99)).await;
let b_item_id = order_b.items[0].id;
let err = db
.returns()
.create_async(CreateReturn {
order_id: order_a.id,
reason: ReturnReason::WrongItem,
reason_details: None,
idempotency_key: None,
items: vec![CreateReturnItem {
order_item_id: b_item_id,
quantity: 1,
condition: None,
}],
notes: None,
})
.await
.expect_err("returning another order's item must be rejected");
assert!(matches!(err, CommerceError::ValidationError(_)), "got {err:?}");
}