141 lines
5.3 KiB
Go
141 lines
5.3 KiB
Go
// 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
|
|
}
|