272 lines
8.8 KiB
Go
272 lines
8.8 KiB
Go
package tests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"gestion/db"
|
|
"gestion/handlers"
|
|
"gestion/models"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// configurePromotionSettings applique les settings donnés (avec Promotions)
|
|
// via testDB.UpdateSettings, comme le ferait l'admin — testDB.UpdateSettings
|
|
// normalise déjà les slices nil, donc ce helper reste minimal.
|
|
func configurePromotionSettings(t *testing.T, settings models.AppSettings) {
|
|
t.Helper()
|
|
if err := testDB.UpdateSettings(settings); err != nil {
|
|
t.Fatalf("UpdateSettings: %v", err)
|
|
}
|
|
}
|
|
|
|
// ── Persistance des settings (save→reload) ──────────────────────────────────
|
|
|
|
func TestUpdateSettings_PromotionsRoundTrip(t *testing.T) {
|
|
resetSettingsAfterTest(t)
|
|
|
|
s := db.DefaultSettings()
|
|
s.PromotionsEnabled = true
|
|
s.Promotions = []models.CategoryPromotionConfig{
|
|
{
|
|
Category: "test",
|
|
DiscountPercent: 15.5,
|
|
AllProducts: false,
|
|
Products: []models.PromotionProductQuantity{
|
|
{ProductID: 111, Quantity: 2},
|
|
{ProductID: 222, Quantity: 1},
|
|
},
|
|
},
|
|
}
|
|
if err := testDB.UpdateSettings(s); err != nil {
|
|
t.Fatalf("UpdateSettings: %v", err)
|
|
}
|
|
|
|
loaded, err := testDB.GetSettings()
|
|
if err != nil {
|
|
t.Fatalf("GetSettings: %v", err)
|
|
}
|
|
if !loaded.PromotionsEnabled {
|
|
t.Fatal("promotions_enabled devrait être true après reload")
|
|
}
|
|
if len(loaded.Promotions) != 1 {
|
|
t.Fatalf("promotions: got=%d want=1: %+v", len(loaded.Promotions), loaded.Promotions)
|
|
}
|
|
promo := loaded.Promotions[0]
|
|
if promo.Category != "test" || promo.DiscountPercent != 15.5 {
|
|
t.Errorf("promo mal persistée: got=%+v", promo)
|
|
}
|
|
if len(promo.Products) != 2 || promo.Products[0].ProductID != 111 || promo.Products[0].Quantity != 2 {
|
|
t.Errorf("products mal persistés: got=%+v", promo.Products)
|
|
}
|
|
|
|
// Désactivation : doit persister à false, pas de résurrection (même
|
|
// classe de bug que TestUpdateSettings_DisablingPointsRewardPersistsAsNil).
|
|
s.PromotionsEnabled = false
|
|
if err := testDB.UpdateSettings(s); err != nil {
|
|
t.Fatalf("UpdateSettings (désactivation): %v", err)
|
|
}
|
|
loaded, err = testDB.GetSettings()
|
|
if err != nil {
|
|
t.Fatalf("GetSettings (désactivation): %v", err)
|
|
}
|
|
if loaded.PromotionsEnabled {
|
|
t.Error("promotions_enabled devrait rester false après désactivation")
|
|
}
|
|
}
|
|
|
|
// ── Résolution de la réduction (logique pure) ───────────────────────────────
|
|
|
|
func TestResolvePromotionDiscount_MatchesAllProductsAtConfiguredQuantity(t *testing.T) {
|
|
settings := &models.AppSettings{
|
|
PromotionsEnabled: true,
|
|
Promotions: []models.CategoryPromotionConfig{
|
|
{Category: "fleurs", DiscountPercent: 20, AllProducts: true, Quantity: 5},
|
|
},
|
|
}
|
|
discount, ok := db.ResolvePromotionDiscount(settings, 42, "fleurs", 5)
|
|
if !ok || discount != 20 {
|
|
t.Errorf("got discount=%.2f ok=%v want=20/true", discount, ok)
|
|
}
|
|
// Mauvaise quantité : pas de promo.
|
|
if _, ok := db.ResolvePromotionDiscount(settings, 42, "fleurs", 3); ok {
|
|
t.Error("ne devrait pas matcher une quantité différente de celle configurée")
|
|
}
|
|
// Mauvaise catégorie : pas de promo.
|
|
if _, ok := db.ResolvePromotionDiscount(settings, 42, "autre", 5); ok {
|
|
t.Error("ne devrait pas matcher une catégorie différente")
|
|
}
|
|
}
|
|
|
|
func TestResolvePromotionDiscount_DisabledReturnsNoDiscount(t *testing.T) {
|
|
settings := &models.AppSettings{
|
|
PromotionsEnabled: false,
|
|
Promotions: []models.CategoryPromotionConfig{
|
|
{Category: "fleurs", DiscountPercent: 20, AllProducts: true, Quantity: 5},
|
|
},
|
|
}
|
|
if _, ok := db.ResolvePromotionDiscount(settings, 42, "fleurs", 5); ok {
|
|
t.Error("aucune promo ne doit s'appliquer si promotions_enabled = false")
|
|
}
|
|
}
|
|
|
|
func TestResolvePromotionDiscount_PerProductSelection(t *testing.T) {
|
|
settings := &models.AppSettings{
|
|
PromotionsEnabled: true,
|
|
Promotions: []models.CategoryPromotionConfig{
|
|
{
|
|
Category: "fleurs",
|
|
AllProducts: false,
|
|
Products: []models.PromotionProductQuantity{
|
|
{ProductID: 1, Quantity: 2},
|
|
},
|
|
DiscountPercent: 10,
|
|
},
|
|
},
|
|
}
|
|
if discount, ok := db.ResolvePromotionDiscount(settings, 1, "fleurs", 2); !ok || discount != 10 {
|
|
t.Errorf("produit sélectionné à la bonne quantité: got discount=%.2f ok=%v", discount, ok)
|
|
}
|
|
if _, ok := db.ResolvePromotionDiscount(settings, 1, "fleurs", 3); ok {
|
|
t.Error("mauvaise quantité pour ce produit : ne doit pas matcher")
|
|
}
|
|
if _, ok := db.ResolvePromotionDiscount(settings, 2, "fleurs", 2); ok {
|
|
t.Error("produit non sélectionné : ne doit pas matcher")
|
|
}
|
|
}
|
|
|
|
// ── AddToBasket applique réellement la réduction au prix facturé ───────────
|
|
|
|
func TestAddToBasket_AppliesPromotionDiscount(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
resetSettingsAfterTest(t)
|
|
username := newTestClient(t, "promo_basket_applies")
|
|
productID := newTestProduct(t, "PromoBasketApplies", 10)
|
|
// 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", DiscountPercent: 20, AllProducts: true, Quantity: 1},
|
|
}
|
|
configurePromotionSettings(t, s)
|
|
|
|
basket, err := testDB.AddToBasket(username, productID, 1)
|
|
if err != nil {
|
|
t.Fatalf("AddToBasket: %v", err)
|
|
}
|
|
if basket.Price != 8.0 {
|
|
t.Errorf("prix attendu = 10€ - 20%% = 8.00€: got=%.2f", basket.Price)
|
|
}
|
|
}
|
|
|
|
func TestAddToBasket_NoDiscountWhenPromotionsDisabled(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
resetSettingsAfterTest(t)
|
|
username := newTestClient(t, "promo_basket_disabled")
|
|
productID := newTestProduct(t, "PromoBasketDisabled", 10)
|
|
|
|
s := db.DefaultSettings()
|
|
s.PromotionsEnabled = false
|
|
s.Promotions = []models.CategoryPromotionConfig{
|
|
{Category: "test", DiscountPercent: 20, AllProducts: true, Quantity: 1},
|
|
}
|
|
configurePromotionSettings(t, s)
|
|
|
|
basket, err := testDB.AddToBasket(username, productID, 1)
|
|
if err != nil {
|
|
t.Fatalf("AddToBasket: %v", err)
|
|
}
|
|
if basket.Price != 10.0 {
|
|
t.Errorf("promotions désactivées: le prix catalogue plein doit s'appliquer: got=%.2f want=10.00", basket.Price)
|
|
}
|
|
}
|
|
|
|
func TestAddToBasket_NoDiscountForDifferentProductSelection(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
resetSettingsAfterTest(t)
|
|
username := newTestClient(t, "promo_basket_other_product")
|
|
promotedID := newTestProduct(t, "PromoBasketOtherPromoted", 10)
|
|
otherID := newTestProduct(t, "PromoBasketOtherPlain", 10)
|
|
|
|
s := db.DefaultSettings()
|
|
s.PromotionsEnabled = true
|
|
s.Promotions = []models.CategoryPromotionConfig{
|
|
{
|
|
Category: "test",
|
|
AllProducts: false,
|
|
Products: []models.PromotionProductQuantity{{ProductID: promotedID, Quantity: 1}},
|
|
DiscountPercent: 50,
|
|
},
|
|
}
|
|
configurePromotionSettings(t, s)
|
|
|
|
promotedBasket, err := testDB.AddToBasket(username, promotedID, 1)
|
|
if err != nil {
|
|
t.Fatalf("AddToBasket (promu): %v", err)
|
|
}
|
|
if promotedBasket.Price != 5.0 {
|
|
t.Errorf("produit promu: prix attendu = 10€ - 50%% = 5.00€: got=%.2f", promotedBasket.Price)
|
|
}
|
|
|
|
otherBasket, err := testDB.AddToBasket(username, otherID, 1)
|
|
if err != nil {
|
|
t.Fatalf("AddToBasket (autre): %v", err)
|
|
}
|
|
if otherBasket.Price != 10.0 {
|
|
t.Errorf("produit non sélectionné dans la promo: prix plein attendu=10.00: got=%.2f", otherBasket.Price)
|
|
}
|
|
}
|
|
|
|
// ── Affichage catalogue : le prix promo est annoté sur le palier concerné ──
|
|
|
|
func TestGetProductByID_AnnotatesPromoPriceOnMatchingTier(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
resetSettingsAfterTest(t)
|
|
productID := newTestProduct(t, "PromoDisplayAnnotated", 10)
|
|
|
|
s := db.DefaultSettings()
|
|
s.PromotionsEnabled = true
|
|
s.Promotions = []models.CategoryPromotionConfig{
|
|
{Category: "test", DiscountPercent: 25, AllProducts: true, Quantity: 1},
|
|
}
|
|
configurePromotionSettings(t, s)
|
|
|
|
idStr := strconv.Itoa(productID)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/products/"+idStr, nil)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = req
|
|
c.Set("database", testDB)
|
|
c.Params = gin.Params{{Key: "id", Value: idStr}}
|
|
c.Set("role", "client")
|
|
handlers.GetProductByID(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
Data models.Product `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("décodage réponse: %v body=%s", err, rec.Body.String())
|
|
}
|
|
if len(resp.Data.Prices) != 1 {
|
|
t.Fatalf("attendu 1 palier de prix: got=%+v", resp.Data.Prices)
|
|
}
|
|
tier := resp.Data.Prices[0]
|
|
if tier.PromoPrice == nil {
|
|
t.Fatal("PromoPrice devrait être renseigné pour ce palier couvert par la promo")
|
|
}
|
|
if *tier.PromoPrice != 7.5 {
|
|
t.Errorf("promo_price attendu = 10€ - 25%% = 7.50€: got=%.2f", *tier.PromoPrice)
|
|
}
|
|
if tier.PromoPercent != 25 {
|
|
t.Errorf("promo_percent attendu=25: got=%.2f", tier.PromoPercent)
|
|
}
|
|
}
|