309 lines
10 KiB
Go
309 lines
10 KiB
Go
package tests
|
||
|
||
import (
|
||
"gestion/db"
|
||
"gestion/models"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// ── Traçage du montant économisé (AddToBasket → command_items) ─────────────
|
||
//
|
||
// command_items.promo_discount / baskets.promo_discount capturent le montant
|
||
// (€) économisé par une promotion de prix au moment de AddToBasket, pour
|
||
// permettre des statistiques historiques fiables même si la configuration de
|
||
// promotion change ensuite (voir db_basket.go, commentaire sur promoDiscount).
|
||
|
||
func TestAddToBasket_TracksPromoDiscountAmount(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "promo_discount_track")
|
||
productID := newTestProduct(t, "PromoDiscountTrack", 20)
|
||
// 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: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
|
||
basket, err := testDB.AddToBasket(username, productID, 1)
|
||
if err != nil {
|
||
t.Fatalf("AddToBasket: %v", err)
|
||
}
|
||
if basket.Price != 8.0 {
|
||
t.Fatalf("précondition prix promo: got=%.2f want=8.00", basket.Price)
|
||
}
|
||
if basket.PromoDiscount != 2.0 {
|
||
t.Errorf("promo_discount doit être l'écart catalogue/promo: got=%.2f want=2.00 (10€-8€)", basket.PromoDiscount)
|
||
}
|
||
}
|
||
|
||
func TestAddToBasket_NoPromoDiscountWithoutPromotion(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "promo_discount_none")
|
||
productID := newTestProduct(t, "PromoDiscountNone", 20)
|
||
|
||
basket, err := testDB.AddToBasket(username, productID, 1)
|
||
if err != nil {
|
||
t.Fatalf("AddToBasket: %v", err)
|
||
}
|
||
if basket.PromoDiscount != 0 {
|
||
t.Errorf("sans promo, promo_discount doit rester à 0: got=%.2f", basket.PromoDiscount)
|
||
}
|
||
}
|
||
|
||
// Deux ajouts successifs du même produit (même ligne panier, is_reward=false)
|
||
// fusionnent quantité et prix (voir AddToBasket) — promo_discount doit être
|
||
// cumulé de la même façon, pas remplacé par le dernier ajout.
|
||
func TestAddToBasket_MergePromoDiscountAccumulatesAcrossAdds(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "promo_discount_merge")
|
||
productID := newTestProduct(t, "PromoDiscountMerge", 20)
|
||
|
||
s := db.DefaultSettings()
|
||
s.PromotionsEnabled = true
|
||
s.Promotions = []models.CategoryPromotionConfig{
|
||
{Category: "test", AllProducts: true, Quantity: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket (1er ajout): %v", err)
|
||
}
|
||
basket, err := testDB.AddToBasket(username, productID, 1)
|
||
if err != nil {
|
||
t.Fatalf("AddToBasket (2e ajout): %v", err)
|
||
}
|
||
if basket.PromoDiscount != 4.0 {
|
||
t.Errorf("le cumul des deux ajouts doit sommer les remises: got=%.2f want=4.00 (2×2€)", basket.PromoDiscount)
|
||
}
|
||
}
|
||
|
||
func TestCheckout_PromoDiscountCopiedToCommandItems(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "promo_discount_checkout")
|
||
productID := newTestProduct(t, "PromoDiscountCheckout", 20)
|
||
|
||
s := db.DefaultSettings()
|
||
s.PromotionsEnabled = true
|
||
s.Promotions = []models.CategoryPromotionConfig{
|
||
{Category: "test", AllProducts: true, Quantity: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket: %v", err)
|
||
}
|
||
|
||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||
if err != nil {
|
||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||
}
|
||
|
||
var discount float64
|
||
if err := testDB.GDB.Raw(
|
||
`SELECT promo_discount FROM command_items WHERE command_id = ? AND product_id = ?`,
|
||
cmd.ID, productID,
|
||
).Scan(&discount).Error; err != nil {
|
||
t.Fatalf("lecture command_items: %v", err)
|
||
}
|
||
if discount != 2.0 {
|
||
t.Errorf("promo_discount doit être copié tel quel au checkout: got=%.2f want=2.00", discount)
|
||
}
|
||
}
|
||
|
||
// ── Stats admin : total économisé et nombre de commandes concernées ────────
|
||
|
||
func approveTestCommand(t *testing.T, commandID int) {
|
||
t.Helper()
|
||
if err := testDB.GDB.Exec(`UPDATE commandes SET status = 'approved' WHERE id = ?`, commandID).Error; err != nil {
|
||
t.Fatalf("passage en approved: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestTotalPromoDiscount_SumsOnlyApprovedOrders(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "stats_promo_discount")
|
||
productID := newTestProduct(t, "StatsPromoDiscount", 20)
|
||
|
||
s := db.DefaultSettings()
|
||
s.PromotionsEnabled = true
|
||
s.Promotions = []models.CategoryPromotionConfig{
|
||
{Category: "test", AllProducts: true, Quantity: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
|
||
// Commande 1 : avec promo, approuvée → comptée.
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket (cmd1): %v", err)
|
||
}
|
||
cmd1, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||
if err != nil {
|
||
t.Fatalf("CreateCommandWithAddress (cmd1): %v", err)
|
||
}
|
||
approveTestCommand(t, cmd1.ID)
|
||
|
||
// Commande 2 : avec promo, restée "pending" (statut par défaut du
|
||
// checkout) → NE DOIT PAS être comptée.
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket (cmd2): %v", err)
|
||
}
|
||
if _, err := testDB.CreateCommandWithAddress(username, "1 rue de test"); err != nil {
|
||
t.Fatalf("CreateCommandWithAddress (cmd2): %v", err)
|
||
}
|
||
|
||
total, err := testDB.TotalPromoDiscount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("TotalPromoDiscount: %v", err)
|
||
}
|
||
if total != 2.0 {
|
||
t.Errorf("seule la commande approuvée doit compter: got=%.2f want=2.00", total)
|
||
}
|
||
|
||
count, err := testDB.PromoOrdersCount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("PromoOrdersCount: %v", err)
|
||
}
|
||
if count != 1 {
|
||
t.Errorf("une seule commande approuvée avec promo: got=%d want=1", count)
|
||
}
|
||
}
|
||
|
||
func TestTotalPromoDiscount_RespectsResetFilter(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "stats_promo_reset")
|
||
productID := newTestProduct(t, "StatsPromoReset", 20)
|
||
|
||
s := db.DefaultSettings()
|
||
s.PromotionsEnabled = true
|
||
s.Promotions = []models.CategoryPromotionConfig{
|
||
{Category: "test", AllProducts: true, Quantity: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket: %v", err)
|
||
}
|
||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||
if err != nil {
|
||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||
}
|
||
approveTestCommand(t, cmd.ID)
|
||
|
||
// Un reset postérieur à la création de la commande doit l'exclure — sert
|
||
// aussi à vérifier que la jointure command_items/commandes qualifie bien
|
||
// created_at par l'alias (les deux tables ont une colonne created_at,
|
||
// donc une clause non qualifiée provoquerait une erreur Postgres
|
||
// "ambiguous column" plutôt qu'un mauvais résultat).
|
||
future := time.Now().Add(time.Hour)
|
||
total, err := testDB.TotalPromoDiscount(future)
|
||
if err != nil {
|
||
t.Fatalf("TotalPromoDiscount: %v", err)
|
||
}
|
||
if total != 0 {
|
||
t.Errorf("commande antérieure au reset: doit être exclue: got=%.2f want=0", total)
|
||
}
|
||
|
||
count, err := testDB.PromoOrdersCount(future)
|
||
if err != nil {
|
||
t.Fatalf("PromoOrdersCount: %v", err)
|
||
}
|
||
if count != 0 {
|
||
t.Errorf("commande antérieure au reset: doit être exclue: got=%d want=0", count)
|
||
}
|
||
}
|
||
|
||
// Une commande avec plusieurs lignes en promo ne doit compter qu'une fois
|
||
// dans PromoOrdersCount (COUNT DISTINCT command_id), mais le montant total
|
||
// doit sommer toutes les lignes.
|
||
func TestPromoOrdersCount_CountsOrderOnceDespiteMultipleDiscountedItems(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "stats_promo_multi")
|
||
productA := newTestProduct(t, "StatsPromoMultiA", 20)
|
||
productB := newTestProduct(t, "StatsPromoMultiB", 20)
|
||
|
||
s := db.DefaultSettings()
|
||
s.PromotionsEnabled = true
|
||
s.Promotions = []models.CategoryPromotionConfig{
|
||
{Category: "test", AllProducts: true, Quantity: 1, DiscountPercent: 20},
|
||
}
|
||
if err := testDB.UpdateSettings(s); err != nil {
|
||
t.Fatalf("UpdateSettings: %v", err)
|
||
}
|
||
if _, err := testDB.AddToBasket(username, productA, 1); err != nil {
|
||
t.Fatalf("AddToBasket A: %v", err)
|
||
}
|
||
if _, err := testDB.AddToBasket(username, productB, 1); err != nil {
|
||
t.Fatalf("AddToBasket B: %v", err)
|
||
}
|
||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||
if err != nil {
|
||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||
}
|
||
approveTestCommand(t, cmd.ID)
|
||
|
||
count, err := testDB.PromoOrdersCount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("PromoOrdersCount: %v", err)
|
||
}
|
||
if count != 1 {
|
||
t.Errorf("une commande avec 2 lignes en promo doit compter une seule fois: got=%d want=1", count)
|
||
}
|
||
|
||
total, err := testDB.TotalPromoDiscount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("TotalPromoDiscount: %v", err)
|
||
}
|
||
if total != 4.0 {
|
||
t.Errorf("le montant total doit sommer les deux lignes: got=%.2f want=4.00 (2×2€)", total)
|
||
}
|
||
}
|
||
|
||
func TestPromoOrdersCount_IgnoresOrdersWithoutDiscount(t *testing.T) {
|
||
cleanupStockTestData(t)
|
||
resetSettingsAfterTest(t)
|
||
username := newTestClient(t, "stats_promo_zero")
|
||
productID := newTestProduct(t, "StatsPromoZero", 20)
|
||
// Aucune promotion configurée : promo_discount reste à 0 pour cette commande.
|
||
|
||
if _, err := testDB.AddToBasket(username, productID, 1); err != nil {
|
||
t.Fatalf("AddToBasket: %v", err)
|
||
}
|
||
cmd, err := testDB.CreateCommandWithAddress(username, "1 rue de test")
|
||
if err != nil {
|
||
t.Fatalf("CreateCommandWithAddress: %v", err)
|
||
}
|
||
approveTestCommand(t, cmd.ID)
|
||
|
||
count, err := testDB.PromoOrdersCount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("PromoOrdersCount: %v", err)
|
||
}
|
||
if count != 0 {
|
||
t.Errorf("aucune commande sans promo ne doit être comptée: got=%d want=0", count)
|
||
}
|
||
total, err := testDB.TotalPromoDiscount(time.Time{})
|
||
if err != nil {
|
||
t.Fatalf("TotalPromoDiscount: %v", err)
|
||
}
|
||
if total != 0 {
|
||
t.Errorf("aucun montant économisé sans promo: got=%.2f want=0", total)
|
||
}
|
||
}
|