// Package tests regroupe les tests de bout en bout de la gestion de stock, // écrits contre l'API publique des paquets db/ et handlers/ (aucun accès à // leurs symboles non exportés) — voir docker-compose.yml pour la base de // test locale nécessaire pour les exécuter (`go test ./tests/...`). package tests import ( "fmt" "gestion/db" "os" "sync/atomic" "testing" "github.com/gin-gonic/gin" ) // testDB est l'instance partagée par tous les tests de ce paquet. Toutes les // données créées utilisent un préfixe dédié (testUserPrefix / testProductPrefix) // et sont nettoyées avant et après chaque test, ce qui rend la suite sans // danger même si elle tourne contre une base partagée. var testDB *db.Database const ( testUserPrefix = "stocktest_" testProductPrefix = "TESTSTOCK_" ) // testPhoneCounter garantit un numéro de téléphone unique par client de test // (colonne UNIQUE sur clients.telephone). var testPhoneCounter int64 func nextTestPhone() string { n := atomic.AddInt64(&testPhoneCounter, 1) return fmt.Sprintf("+3361%09d", n) } func TestMain(m *testing.M) { gin.SetMode(gin.TestMode) testDB = db.InitDB() db.InitRedis() os.Exit(m.Run()) } // cleanupStockTestData supprime toutes les données créées par les tests de // gestion de stock (identifiées par leur préfixe), dans le bon ordre pour // respecter les contraintes de clé étrangère. func cleanupStockTestData(t *testing.T) { t.Helper() testDB.GDB.Exec(`DELETE FROM command_items WHERE command_id IN (SELECT id FROM commandes WHERE username LIKE ?)`, testUserPrefix+"%") testDB.GDB.Exec(`DELETE FROM commandes WHERE username LIKE ?`, testUserPrefix+"%") testDB.GDB.Exec(`DELETE FROM baskets WHERE username LIKE ?`, testUserPrefix+"%") testDB.GDB.Exec(`DELETE FROM clients WHERE username LIKE ?`, testUserPrefix+"%") testDB.GDB.Exec(`DELETE FROM product_prices WHERE product_id IN (SELECT id FROM products WHERE name LIKE ?)`, testProductPrefix+"%") testDB.GDB.Exec(`DELETE FROM products WHERE name LIKE ?`, testProductPrefix+"%") } // newTestProduct crée un produit de test avec un stock initial donné et un // prix actif pour quantity=1, et programme son nettoyage en fin de test. func newTestProduct(t *testing.T, name string, stock float64) int { t.Helper() fullName := testProductPrefix + name var id int if err := testDB.GDB.Raw( `INSERT INTO products (name, category, description, stock) VALUES (?, 'test', '', ?) RETURNING id`, fullName, stock, ).Scan(&id).Error; err != nil { t.Fatalf("création produit test %q: %v", fullName, err) } if err := testDB.GDB.Exec( `INSERT INTO product_prices (product_id, quantity, price, active_price) VALUES (?, 1, 10.00, true)`, id, ).Error; err != nil { t.Fatalf("création prix produit test %q: %v", fullName, err) } t.Cleanup(func() { testDB.GDB.Exec(`DELETE FROM product_prices WHERE product_id = ?`, id) testDB.GDB.Exec(`DELETE FROM products WHERE id = ?`, id) }) return id } // productStock relit le stock courant d'un produit directement en base. func productStock(t *testing.T, productID int) float64 { t.Helper() var stock float64 if err := testDB.GDB.Raw(`SELECT stock FROM products WHERE id = ?`, productID).Scan(&stock).Error; err != nil { t.Fatalf("lecture stock produit %d: %v", productID, err) } return stock } // newTestClient crée un client de test et programme son nettoyage en fin de test. func newTestClient(t *testing.T, name string) string { t.Helper() username := testUserPrefix + name if err := testDB.GDB.Exec( `INSERT INTO clients (username, password, nom, prenom, telephone) VALUES (?, 'x', 'T', 'C', ?) ON CONFLICT (username) DO NOTHING`, username, nextTestPhone(), ).Error; err != nil { t.Fatalf("création client test %q: %v", username, err) } t.Cleanup(func() { testDB.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username) testDB.GDB.Exec(`DELETE FROM command_items WHERE command_id IN (SELECT id FROM commandes WHERE username = ?)`, username) testDB.GDB.Exec(`DELETE FROM commandes WHERE username = ?`, username) testDB.GDB.Exec(`DELETE FROM clients WHERE username = ?`, username) }) return username } // newTestCommandWithItem crée directement une commande avec un item (en // contournant le checkout), pour tester isolément les chemins d'annulation // et de remboursement de stock. Retourne l'ID de la commande créée. func newTestCommandWithItem(t *testing.T, username, status, livreurAssign string, productID int, quantite float64, prix float64) int { t.Helper() var cmdID int if err := testDB.GDB.Raw( `INSERT INTO commandes (username, status, livreur_assign, adresse, total_prix, created_at, updated_at) VALUES (?, ?, NULLIF(?, ''), 'Adresse test', ?, NOW(), NOW()) RETURNING id`, username, status, livreurAssign, prix, ).Scan(&cmdID).Error; err != nil { t.Fatalf("création commande 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 test: %v", err) } return cmdID } func commandStatus(t *testing.T, commandID int) string { t.Helper() var status string testDB.GDB.Raw(`SELECT status FROM commandes WHERE id = ?`, commandID).Scan(&status) return status }