209 lines
7.6 KiB
Go
209 lines
7.6 KiB
Go
package tests
|
|
|
|
import (
|
|
"gestion/models"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// newTestOrderForStats crée directement une commande (+ un item) avec un
|
|
// statut, un total, un crédit de parrainage utilisé et une date de création
|
|
// contrôlés, pour tester isolément les calculs de stats admin.
|
|
func newTestOrderForStats(t *testing.T, username, status string, productID int, quantite, prix, referralUsed float64, createdAt time.Time) int {
|
|
t.Helper()
|
|
var cmdID int
|
|
if err := testDB.GDB.Raw(
|
|
`INSERT INTO commandes (username, status, adresse, total_prix, referral_used, created_at, updated_at)
|
|
VALUES (?, ?, 'Adresse test', ?, ?, ?, ?) RETURNING id`,
|
|
username, status, prix, referralUsed, createdAt, createdAt,
|
|
).Scan(&cmdID).Error; err != nil {
|
|
t.Fatalf("création commande stats test: %v", err)
|
|
}
|
|
if err := testDB.GDB.Exec(
|
|
`INSERT INTO command_items (command_id, product_id, produit, quantite, prix, status)
|
|
VALUES (?, ?, 'item test', ?, ?, 'pending')`,
|
|
cmdID, productID, quantite, prix,
|
|
).Error; err != nil {
|
|
t.Fatalf("création item stats test: %v", err)
|
|
}
|
|
return cmdID
|
|
}
|
|
|
|
// TotalRevenue ne compte que les commandes approuvées, nettes du crédit de
|
|
// parrainage utilisé — les commandes annulées ou encore en cours sont exclues.
|
|
func TestTotalRevenue_NetsReferralAndExcludesNonApproved(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_total_revenue")
|
|
productID := newTestProduct(t, "StatsTotalRevenue", 100)
|
|
now := time.Now()
|
|
|
|
newTestOrderForStats(t, username, "approved", productID, 5, 100, 30, now) // net 70
|
|
newTestOrderForStats(t, username, "approved", productID, 2, 50, 0, now) // net 50
|
|
newTestOrderForStats(t, username, "cancelled", productID, 9, 999, 0, now) // exclue
|
|
newTestOrderForStats(t, username, "pending", productID, 9, 999, 0, now) // exclue (pas encore approuvée)
|
|
|
|
total, err := testDB.TotalRevenue(time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("TotalRevenue: %v", err)
|
|
}
|
|
if total != 120 {
|
|
t.Errorf("TotalRevenue: got=%.2f want=120 (70+50, parrainage déduit, annulée/pending exclues)", total)
|
|
}
|
|
}
|
|
|
|
// TotalOrders exclut uniquement les commandes annulées (contrairement à
|
|
// TotalRevenue, il compte aussi les commandes non encore approuvées).
|
|
func TestTotalOrders_ExcludesOnlyCancelled(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_total_orders")
|
|
productID := newTestProduct(t, "StatsTotalOrders", 100)
|
|
now := time.Now()
|
|
|
|
newTestOrderForStats(t, username, "pending", productID, 1, 10, 0, now)
|
|
newTestOrderForStats(t, username, "approved", productID, 1, 10, 0, now)
|
|
newTestOrderForStats(t, username, "cancelled", productID, 1, 10, 0, now)
|
|
|
|
total, err := testDB.TotalOrders(time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("TotalOrders: %v", err)
|
|
}
|
|
if total != 2 {
|
|
t.Errorf("TotalOrders: got=%d want=2 (pending+approved, cancelled exclue)", total)
|
|
}
|
|
}
|
|
|
|
// Régression du bug corrigé : le revenu par produit/catégorie (TopProducts,
|
|
// QuantityBreakdown, DailyProductDetailForDate) doit rester cohérent avec
|
|
// TotalRevenue même quand une commande approuvée a utilisé du crédit de
|
|
// parrainage — avant le fix, ces trois vues sommaient ci.prix brut sans
|
|
// déduire referral_used, produisant un total supérieur au résumé global.
|
|
func TestProductBreakdowns_RevenueMatchesTotalRevenue_WithReferralUsed(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_breakdown_referral")
|
|
productA := newTestProduct(t, "StatsBreakdownA", 100)
|
|
productB := newTestProduct(t, "StatsBreakdownB", 100)
|
|
now := time.Now()
|
|
|
|
newTestOrderForStats(t, username, "approved", productA, 5, 100, 30, now) // net 70
|
|
newTestOrderForStats(t, username, "approved", productB, 2, 50, 0, now) // net 50
|
|
|
|
totalRevenue, err := testDB.TotalRevenue(time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("TotalRevenue: %v", err)
|
|
}
|
|
if totalRevenue != 120 {
|
|
t.Fatalf("précondition TotalRevenue: got=%.2f want=120", totalRevenue)
|
|
}
|
|
|
|
var prodRows []models.ProductRow
|
|
if err := testDB.TopProducts(&prodRows, time.Time{}, 15); err != nil {
|
|
t.Fatalf("TopProducts: %v", err)
|
|
}
|
|
sumTop := 0.0
|
|
for _, r := range prodRows {
|
|
sumTop += r.Revenue
|
|
}
|
|
if sumTop != totalRevenue {
|
|
t.Errorf("somme TopProducts.revenue = %.2f, doit correspondre à TotalRevenue = %.2f", sumTop, totalRevenue)
|
|
}
|
|
|
|
var qtyRows []models.QuantityBreakdownRow
|
|
if err := testDB.QuantityBreakdown(&qtyRows, time.Time{}); err != nil {
|
|
t.Fatalf("QuantityBreakdown: %v", err)
|
|
}
|
|
sumQty := 0.0
|
|
for _, r := range qtyRows {
|
|
sumQty += r.Revenue
|
|
}
|
|
if sumQty != totalRevenue {
|
|
t.Errorf("somme QuantityBreakdown.revenue = %.2f, doit correspondre à TotalRevenue = %.2f", sumQty, totalRevenue)
|
|
}
|
|
|
|
var dailyRows []models.DailyProductRow
|
|
if err := testDB.DailyProductDetailForDate(&dailyRows, now); err != nil {
|
|
t.Fatalf("DailyProductDetailForDate: %v", err)
|
|
}
|
|
sumDaily := 0.0
|
|
for _, r := range dailyRows {
|
|
sumDaily += r.Revenue
|
|
}
|
|
if sumDaily != totalRevenue {
|
|
t.Errorf("somme DailyProductDetailForDate.revenue = %.2f, doit correspondre à TotalRevenue = %.2f", sumDaily, totalRevenue)
|
|
}
|
|
}
|
|
|
|
// Cas limite : commande entièrement couverte par le crédit de parrainage
|
|
// (total_prix == referral_used) — la part de revenu attribuée à l'article
|
|
// doit être 0, sans division par zéro ni erreur SQL.
|
|
func TestProductBreakdowns_FullyCoveredByReferralYieldsZeroRevenue(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_breakdown_full_referral")
|
|
productID := newTestProduct(t, "StatsBreakdownFullReferral", 100)
|
|
now := time.Now()
|
|
|
|
newTestOrderForStats(t, username, "approved", productID, 4, 40, 40, now)
|
|
|
|
var prodRows []models.ProductRow
|
|
if err := testDB.TopProducts(&prodRows, time.Time{}, 15); err != nil {
|
|
t.Fatalf("TopProducts: %v", err)
|
|
}
|
|
if len(prodRows) != 1 {
|
|
t.Fatalf("attendu 1 produit, got=%d", len(prodRows))
|
|
}
|
|
if prodRows[0].Revenue != 0 {
|
|
t.Errorf("revenu attendu à 0 pour une commande entièrement couverte par le parrainage: got=%.2f", prodRows[0].Revenue)
|
|
}
|
|
}
|
|
|
|
// RevenueByDayLast30 doit rester cohérent avec TotalRevenue pour des
|
|
// commandes créées aujourd'hui (dans la fenêtre des 30 derniers jours).
|
|
func TestRevenueByDayLast30_MatchesTotalRevenue(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_revenue_by_day")
|
|
productID := newTestProduct(t, "StatsRevenueByDay", 100)
|
|
now := time.Now()
|
|
|
|
newTestOrderForStats(t, username, "approved", productID, 3, 90, 20, now) // net 70
|
|
|
|
totalRevenue, err := testDB.TotalRevenue(time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("TotalRevenue: %v", err)
|
|
}
|
|
|
|
var dayRevRows []models.DayRevenueRow
|
|
if err := testDB.RevenueByDayLast30(&dayRevRows, time.Time{}); err != nil {
|
|
t.Fatalf("RevenueByDayLast30: %v", err)
|
|
}
|
|
sum := 0.0
|
|
for _, r := range dayRevRows {
|
|
sum += r.Revenue
|
|
}
|
|
if sum != totalRevenue {
|
|
t.Errorf("somme RevenueByDayLast30 = %.2f, doit correspondre à TotalRevenue = %.2f", sum, totalRevenue)
|
|
}
|
|
}
|
|
|
|
// DailyProductDetailForDate ne doit inclure que les commandes du jour demandé.
|
|
func TestDailyProductDetailForDate_OnlyIncludesGivenDate(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_daily_date_filter")
|
|
productID := newTestProduct(t, "StatsDailyDateFilter", 100)
|
|
today := time.Now()
|
|
yesterday := today.AddDate(0, 0, -1)
|
|
|
|
newTestOrderForStats(t, username, "approved", productID, 1, 10, 0, today)
|
|
newTestOrderForStats(t, username, "approved", productID, 1, 20, 0, yesterday)
|
|
|
|
var rows []models.DailyProductRow
|
|
if err := testDB.DailyProductDetailForDate(&rows, today); err != nil {
|
|
t.Fatalf("DailyProductDetailForDate: %v", err)
|
|
}
|
|
sum := 0.0
|
|
for _, r := range rows {
|
|
sum += r.Revenue
|
|
}
|
|
if sum != 10 {
|
|
t.Errorf("revenu du jour ne doit inclure que la commande d'aujourd'hui: got=%.2f want=10", sum)
|
|
}
|
|
}
|