chore: build
This commit is contained in:
@@ -210,3 +210,186 @@ func TestAddToBasket_FreeGiftRejectedWhenStockInsufficientForBonus(t *testing.T)
|
||||
t.Fatal("stock=10 ne doit pas suffire pour livrer 10g + 1g offert")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Intégration checkout : le bonus offert est bien décompté du stock ──────
|
||||
//
|
||||
// AddToBasket stocke déjà quantity = demandée + offerte (voir tests
|
||||
// ci-dessus) ; CreateCommandWithAddress ne relit ni ne recalcule cette
|
||||
// quantité — elle est copiée telle quelle dans command_items.quantite et
|
||||
// utilisée telle quelle pour décrémenter products.stock (db_commands.go).
|
||||
// Ces tests vérifient ce chemin de bout en bout, pas juste AddToBasket isolé.
|
||||
|
||||
func TestCheckout_FreeGiftBonusQuantityDecrementsStock(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
resetSettingsAfterTest(t)
|
||||
username := newTestClient(t, "freegift_checkout_stock")
|
||||
productID := newTestProduct(t, "FreeGiftCheckoutStock", 50)
|
||||
// newTestProduct crée un palier quantity=1 à 10.00€ dans la catégorie "test".
|
||||
|
||||
s := db.DefaultSettings()
|
||||
s.FreeGiftsEnabled = true
|
||||
s.FreeGifts = []models.CategoryFreeGiftConfig{
|
||||
{Category: "test", AllProducts: true, Tiers: []models.FreeGiftTier{
|
||||
{BuyQuantity: 10, FreeQuantity: 1},
|
||||
}},
|
||||
}
|
||||
if err := testDB.UpdateSettings(s); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
|
||||
if _, err := testDB.AddToBasket(username, productID, 10); err != nil {
|
||||
t.Fatalf("AddToBasket: %v", err)
|
||||
}
|
||||
|
||||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||||
}
|
||||
|
||||
// 50 initial - (10 demandés + 1 offert) = 39, pas 40.
|
||||
if got := productStock(t, productID); got != 39 {
|
||||
t.Errorf("stock après checkout avec cadeau: got=%.2f want=39 (50 - 11)", got)
|
||||
}
|
||||
|
||||
var item struct {
|
||||
Quantite float64 `gorm:"column:quantite"`
|
||||
Prix float64 `gorm:"column:prix"`
|
||||
}
|
||||
if err := testDB.GDB.Raw(
|
||||
`SELECT quantite, prix FROM command_items WHERE command_id = ? AND product_id = ?`,
|
||||
cmd.ID, productID,
|
||||
).Scan(&item).Error; err != nil {
|
||||
t.Fatalf("lecture command_items: %v", err)
|
||||
}
|
||||
if item.Quantite != 11 {
|
||||
t.Errorf("command_items.quantite doit inclure le cadeau: got=%.2f want=11", item.Quantite)
|
||||
}
|
||||
if item.Prix != 10.0 {
|
||||
t.Errorf("command_items.prix ne doit pas changer (facturé sur les 10g demandés): got=%.2f want=10.00", item.Prix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckout_FreeGiftRollsBackWhenStockInsufficientForBonus(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
resetSettingsAfterTest(t)
|
||||
username := newTestClient(t, "freegift_checkout_rollback")
|
||||
productID := newTestProduct(t, "FreeGiftCheckoutRollback", 50)
|
||||
|
||||
s := db.DefaultSettings()
|
||||
s.FreeGiftsEnabled = true
|
||||
s.FreeGifts = []models.CategoryFreeGiftConfig{
|
||||
{Category: "test", AllProducts: true, Tiers: []models.FreeGiftTier{
|
||||
{BuyQuantity: 10, FreeQuantity: 1},
|
||||
}},
|
||||
}
|
||||
if err := testDB.UpdateSettings(s); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
if _, err := testDB.AddToBasket(username, productID, 10); err != nil {
|
||||
t.Fatalf("AddToBasket: %v", err)
|
||||
}
|
||||
|
||||
// Le stock chute sous 11 (10 demandés + 1 offert) après l'ajout au panier,
|
||||
// simulant une vente concurrente qui vide le stock entre AddToBasket et
|
||||
// checkout — le checkout doit échouer et ne rien décrémenter.
|
||||
if err := testDB.GDB.Exec(`UPDATE products SET stock = 10 WHERE id = ?`, productID).Error; err != nil {
|
||||
t.Fatalf("réduction stock: %v", err)
|
||||
}
|
||||
|
||||
if _, err := testDB.CreateCommandWithAddress(username, "1 rue de test"); err == nil {
|
||||
t.Fatal("checkout attendu en échec: stock=10 insuffisant pour 10 demandés + 1 offert")
|
||||
}
|
||||
if got := productStock(t, productID); got != 10 {
|
||||
t.Errorf("stock ne doit pas bouger si le checkout échoue: got=%.2f want=10", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Intégration annulation : le remboursement inclut le bonus offert ───────
|
||||
|
||||
func TestCancelCommandAtomic_RefundsFreeGiftBonusQuantity(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
resetSettingsAfterTest(t)
|
||||
username := newTestClient(t, "freegift_cancel_refund")
|
||||
productID := newTestProduct(t, "FreeGiftCancelRefund", 50)
|
||||
|
||||
s := db.DefaultSettings()
|
||||
s.FreeGiftsEnabled = true
|
||||
s.FreeGifts = []models.CategoryFreeGiftConfig{
|
||||
{Category: "test", AllProducts: true, Tiers: []models.FreeGiftTier{
|
||||
{BuyQuantity: 10, FreeQuantity: 1},
|
||||
}},
|
||||
}
|
||||
if err := testDB.UpdateSettings(s); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
if _, err := testDB.AddToBasket(username, productID, 10); err != nil {
|
||||
t.Fatalf("AddToBasket: %v", err)
|
||||
}
|
||||
|
||||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||||
}
|
||||
if got := productStock(t, productID); got != 39 {
|
||||
t.Fatalf("précondition stock post-checkout: got=%.2f want=39", got)
|
||||
}
|
||||
|
||||
if _, err := testDB.CancelCommandAtomic(cmd.ID, username, "test", false); err != nil {
|
||||
t.Fatalf("CancelCommandAtomic: %v", err)
|
||||
}
|
||||
|
||||
// 39 + 11 (10 demandés + 1 offert) = 50, retour exact au stock initial.
|
||||
if got := productStock(t, productID); got != 50 {
|
||||
t.Errorf("stock après annulation (bonus offert inclus dans le remboursement): got=%.2f want=50", got)
|
||||
}
|
||||
|
||||
// Rejeu : ne doit rembourser qu'une fois.
|
||||
if _, err := testDB.CancelCommandAtomic(cmd.ID, username, "test", false); err == nil {
|
||||
t.Fatal("le second appel sur une commande déjà annulée doit échouer, pas rembourser une seconde fois")
|
||||
}
|
||||
if got := productStock(t, productID); got != 50 {
|
||||
t.Errorf("stock après double annulation: got=%.2f want=50 (un seul remboursement)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cumul avec les promotions de prix ───────────────────────────────────────
|
||||
//
|
||||
// Une offre "achetez X, Y offert" et une promotion de réduction (%) sur le
|
||||
// même produit doivent pouvoir s'appliquer ensemble : la promotion réduit le
|
||||
// prix facturé sur la quantité demandée, le cadeau ajoute de la quantité
|
||||
// livrée sans toucher au prix — les deux mécanismes sont indépendants dans
|
||||
// AddToBasket (voir db_basket.go) mais rien ne garantissait jusqu'ici qu'ils
|
||||
// ne s'écrasent pas mutuellement une fois combinés.
|
||||
func TestAddToBasket_FreeGiftAndPromotionBothApplyTogether(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
resetSettingsAfterTest(t)
|
||||
username := newTestClient(t, "freegift_promo_combo")
|
||||
productID := newTestProduct(t, "FreeGiftPromoCombo", 50)
|
||||
// newTestProduct crée un palier quantity=1 à 10.00€ dans la catégorie "test".
|
||||
|
||||
s := db.DefaultSettings()
|
||||
s.PromotionsEnabled = true
|
||||
s.Promotions = []models.CategoryPromotionConfig{
|
||||
{Category: "test", AllProducts: true, Quantity: 10, DiscountPercent: 20},
|
||||
}
|
||||
s.FreeGiftsEnabled = true
|
||||
s.FreeGifts = []models.CategoryFreeGiftConfig{
|
||||
{Category: "test", AllProducts: true, Tiers: []models.FreeGiftTier{
|
||||
{BuyQuantity: 10, FreeQuantity: 1},
|
||||
}},
|
||||
}
|
||||
if err := testDB.UpdateSettings(s); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
|
||||
basket, err := testDB.AddToBasket(username, productID, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("AddToBasket: %v", err)
|
||||
}
|
||||
if basket.Quantity != 11 {
|
||||
t.Errorf("le cadeau doit s'appliquer malgré la promo active: got quantity=%.2f want=11", basket.Quantity)
|
||||
}
|
||||
if basket.Price != 8.0 {
|
||||
t.Errorf("la promo doit s'appliquer malgré le cadeau actif: got price=%.2f want=8.00 (10€ - 20%%)", basket.Price)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user