117 lines
4.0 KiB
Go
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")
|
|
}
|
|
}
|