163 lines
5.5 KiB
Go
163 lines
5.5 KiB
Go
package tests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gestion/handlers"
|
|
"gestion/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func adminStatsContext() (*gin.Context, *httptest.ResponseRecorder) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/stats", nil)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = req
|
|
c.Set("database", testDB)
|
|
return c, rec
|
|
}
|
|
|
|
// insertStatsCommand insère une commande directement avec un created_at
|
|
// choisi, pour contrôler précisément le jour de semaine/heure agrégés.
|
|
func insertStatsCommand(t *testing.T, username, status, createdAt string) {
|
|
t.Helper()
|
|
testDB.GDB.Exec(
|
|
`INSERT INTO commandes (username, status, adresse, total_prix, created_at, updated_at)
|
|
VALUES (?, ?, 'Adresse stats test', 10, ?::timestamp, ?::timestamp)`,
|
|
username, status, createdAt, createdAt,
|
|
)
|
|
}
|
|
|
|
func TestGetAdminStats_ZeroOrdersAvgPerDayIsZeroNoPanic(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
// Table commandes peut contenir des données d'autres tests, mais aucune
|
|
// n'utilise ce username isolé — on vérifie juste l'absence de panic/NaN
|
|
// et que la structure de réponse est bien formée avec les vraies
|
|
// données actuelles de la base de test (qui peut être non-vide).
|
|
c, rec := adminStatsContext()
|
|
handlers.GetAdminStats(c)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("GetAdminStats doit réussir: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var resp struct {
|
|
Summary struct {
|
|
AvgPerDay float64 `json:"avg_per_day"`
|
|
} `json:"summary"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("réponse JSON invalide: %v", err)
|
|
}
|
|
if resp.Summary.AvgPerDay < 0 {
|
|
t.Errorf("avg_per_day ne doit jamais être négatif: got=%f", resp.Summary.AvgPerDay)
|
|
}
|
|
}
|
|
|
|
func TestGetAdminStats_PeakWeekdayMatchesBusiestDay(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_peak")
|
|
// 2024-01-08 est un lundi, 2024-01-07 un dimanche (DOW Postgres: 0=dimanche).
|
|
insertStatsCommand(t, username, "pending", "2024-01-08 10:00:00")
|
|
insertStatsCommand(t, username, "pending", "2024-01-08 11:00:00")
|
|
insertStatsCommand(t, username, "pending", "2024-01-08 12:00:00")
|
|
insertStatsCommand(t, username, "pending", "2024-01-07 10:00:00")
|
|
|
|
c, rec := adminStatsContext()
|
|
handlers.GetAdminStats(c)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("GetAdminStats doit réussir: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var resp struct {
|
|
Summary struct {
|
|
PeakWeekday string `json:"peak_weekday"`
|
|
} `json:"summary"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("réponse JSON invalide: %v", err)
|
|
}
|
|
if resp.Summary.PeakWeekday != "Lundi" {
|
|
t.Errorf("peak_weekday doit être le jour avec le plus de commandes: got=%q want=Lundi", resp.Summary.PeakWeekday)
|
|
}
|
|
}
|
|
|
|
func TestGetAdminStats_ByQuantitySortedDescendingByTotalOrders(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_byqty")
|
|
prodLow := newTestProduct(t, "ByQtyLow", 100)
|
|
prodHigh := newTestProduct(t, "ByQtyHigh", 100)
|
|
|
|
// prodHigh: 3 commandes ; prodLow: 1 commande.
|
|
for range 3 {
|
|
newTestCommandWithItem(t, username, "pending", "", prodHigh, 1, 10)
|
|
}
|
|
newTestCommandWithItem(t, username, "pending", "", prodLow, 1, 10)
|
|
|
|
c, rec := adminStatsContext()
|
|
handlers.GetAdminStats(c)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("GetAdminStats doit réussir: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var resp struct {
|
|
ByQuantity []struct {
|
|
ProductID int `json:"product_id"`
|
|
TotalOrders int `json:"total_orders"`
|
|
} `json:"by_quantity"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("réponse JSON invalide: %v", err)
|
|
}
|
|
// Vérifie l'ordre décroissant global (pas seulement nos deux produits,
|
|
// d'autres tests peuvent avoir laissé des données) et que prodHigh
|
|
// apparaît avant prodLow.
|
|
highIdx, lowIdx := -1, -1
|
|
for i, r := range resp.ByQuantity {
|
|
if r.ProductID == prodHigh {
|
|
highIdx = i
|
|
}
|
|
if r.ProductID == prodLow {
|
|
lowIdx = i
|
|
}
|
|
if i > 0 && r.TotalOrders > resp.ByQuantity[i-1].TotalOrders {
|
|
t.Errorf("by_quantity doit être trié par total_orders décroissant: rupture à l'index %d", i)
|
|
}
|
|
}
|
|
if highIdx == -1 || lowIdx == -1 {
|
|
t.Fatalf("les deux produits de test doivent apparaître dans by_quantity: highIdx=%d lowIdx=%d", highIdx, lowIdx)
|
|
}
|
|
if highIdx >= lowIdx {
|
|
t.Errorf("le produit avec le plus de commandes doit apparaître avant: highIdx=%d lowIdx=%d", highIdx, lowIdx)
|
|
}
|
|
}
|
|
|
|
func TestOrdersAndRevenueByHour_CountsNonCancelledRevenueOnlyApproved(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "stats_hour")
|
|
insertStatsCommand(t, username, "approved", "2024-01-08 14:00:00")
|
|
insertStatsCommand(t, username, "pending", "2024-01-08 14:30:00")
|
|
insertStatsCommand(t, username, "cancelled", "2024-01-08 14:45:00")
|
|
testDB.GDB.Exec(`UPDATE commandes SET total_prix = 25 WHERE username = ? AND status = 'approved'`, username)
|
|
|
|
var hourRows []models.HourRow
|
|
if err := testDB.OrdersAndRevenueByHour(&hourRows, testDB.ReadResetAt("stats_reset_heures_at")); err != nil {
|
|
t.Fatalf("OrdersAndRevenueByHour: %v", err)
|
|
}
|
|
var found bool
|
|
for _, r := range hourRows {
|
|
if r.Hour == 14 {
|
|
found = true
|
|
if r.Count != 2 {
|
|
t.Errorf("count à 14h doit exclure la commande annulée: got=%d want=2", r.Count)
|
|
}
|
|
if r.Revenue != 25 {
|
|
t.Errorf("revenue à 14h ne doit compter que les commandes approuvées: got=%.2f want=25", r.Revenue)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("aucune ligne pour l'heure 14 trouvée")
|
|
}
|
|
}
|