chore: build
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user