Files
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

117 lines
4.0 KiB
Go

package tests
import (
"sync"
"testing"
)
func firstItemID(t *testing.T, commandID int) int {
t.Helper()
var itemID int
if err := testDB.GDB.Raw(`SELECT id FROM command_items WHERE command_id = ? LIMIT 1`, commandID).Scan(&itemID).Error; err != nil {
t.Fatalf("lecture item de la commande %d: %v", commandID, err)
}
return itemID
}
// Supprimer un item d'une commande encore active doit restituer le stock de
// cet item.
func TestDeleteCommandItem_RestoresStockOnActiveOrder(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "delitem_active")
productID := newTestProduct(t, "DelItemActive", 5)
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 3, 30)
itemID := firstItemID(t, cmdID)
if err := testDB.DeleteCommandItem(cmdID, itemID); err != nil {
t.Fatalf("DeleteCommandItem: %v", err)
}
if got := productStock(t, productID); got != 8 {
t.Errorf("stock après suppression d'item sur commande active (5 initial + 3 remboursés): got=%.2f want=8", got)
}
var remaining int64
testDB.GDB.Raw(`SELECT COUNT(*) FROM command_items WHERE id = ?`, itemID).Scan(&remaining)
if remaining != 0 {
t.Errorf("l'item doit être supprimé: got=%d lignes restantes", remaining)
}
}
// Sur une commande déjà terminale (approuvée/livrée/annulée), le stock a
// déjà été traité par le chemin correspondant — supprimer un item a
// posteriori ne doit pas rembourser une seconde fois.
func TestDeleteCommandItem_DoesNotRestoreStockOnTerminalOrder(t *testing.T) {
for _, status := range []string{"approved", "livre", "cancelled"} {
t.Run(status, func(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "delitem_"+status)
productID := newTestProduct(t, "DelItem"+status, 5)
cmdID := newTestCommandWithItem(t, username, status, "", productID, 3, 30)
itemID := firstItemID(t, cmdID)
if err := testDB.DeleteCommandItem(cmdID, itemID); err != nil {
t.Fatalf("DeleteCommandItem: %v", err)
}
if got := productStock(t, productID); got != 5 {
t.Errorf("stock ne doit pas bouger pour une commande %q: got=%.2f want=5", status, got)
}
})
}
}
// DeleteCommandItem verrouille désormais le statut de la commande (FOR
// UPDATE) avant de décider de rembourser, dans la même transaction — ce test
// vérifie qu'une suppression d'item et une annulation complète de la même
// commande, déclenchées en concurrence, ne remboursent le stock qu'une
// seule fois (peu importe laquelle des deux "gagne" la course).
func TestDeleteCommandItem_ConcurrentWithFullCancelDoesNotDoubleRefund(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "delitem_concurrent")
productID := newTestProduct(t, "DelItemConcurrent", 5)
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 3, 30)
itemID := firstItemID(t, cmdID)
var wg sync.WaitGroup
start := make(chan struct{})
wg.Add(2)
go func() {
defer wg.Done()
<-start
_ = testDB.DeleteCommandItem(cmdID, itemID)
}()
go func() {
defer wg.Done()
<-start
_, _ = testDB.CancelCommandAtomic(cmdID, username, "test", false)
}()
close(start)
wg.Wait()
if got := productStock(t, productID); got != 8 {
t.Errorf("stock après suppression d'item + annulation complète concurrentes (5 initial + 3 remboursés une seule fois attendu): got=%.2f want=8", got)
}
}
// ── SetProductStock (override direct admin) ─────────────────────────────────
func TestSetProductStock_SetsExactValue(t *testing.T) {
cleanupStockTestData(t)
productID := newTestProduct(t, "SetStockDirect", 5)
if err := testDB.SetProductStock(productID, 42); err != nil {
t.Fatalf("SetProductStock: %v", err)
}
if got := productStock(t, productID); got != 42 {
t.Errorf("stock après SetProductStock: got=%.2f want=42", got)
}
}
func TestSetProductStock_RejectsUnknownProduct(t *testing.T) {
cleanupStockTestData(t)
if err := testDB.SetProductStock(-1, 10); err == nil {
t.Fatal("attendu une erreur pour un produit inexistant")
}
}