fix(api): decrement SKU stock only when remaining quantity is sufficient
Prevent oversell by conditioning stock updates and locking SKUs in primary-key order, and record the same concurrent-counter rule in the API spec and agent guide. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
53548196f8
commit
835afbe5e0
@@ -79,6 +79,25 @@ async fn checkout_rejects_empty_cart() {
|
||||
assert!(matches!(err, ApiError::BadRequest(m) if m.contains("empty")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn decrement_stock_rejects_when_guard_fails() {
|
||||
let state = common::spawn_state().await;
|
||||
let sku_id = sellable_sku(&state, "guard", 100, 1).await;
|
||||
let mut tx = state.db.begin().await.unwrap();
|
||||
let err = order::repo::decrement_stock(&mut tx, sku_id, 2)
|
||||
.await
|
||||
.unwrap_err();
|
||||
tx.rollback().await.unwrap();
|
||||
assert!(matches!(err, ApiError::Conflict(m) if m.contains("insufficient stock")));
|
||||
let left: i32 = sqlx::query_scalar("SELECT stock FROM skus WHERE id = $1")
|
||||
.bind(sku_id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(left, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn checkout_rejects_insufficient_stock() {
|
||||
|
||||
Reference in New Issue
Block a user