This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gestion/db"
|
||||
"gestion/models"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// newTestProductWithCategory crée un produit de test avec une catégorie
|
||||
// personnalisée (contrairement à newTestProduct qui pose toujours "test") —
|
||||
// nécessaire ici pour distinguer les catégories dans le routage par livreur.
|
||||
func newTestProductWithCategory(t *testing.T, name, category 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 (?, ?, '', ?) RETURNING id`,
|
||||
fullName, category, 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
|
||||
}
|
||||
|
||||
// setLivreurStatus place directement en Redis le statut d'un livreur, comme
|
||||
// le ferait l'app livreur en production (clé "delivery:status:{username}").
|
||||
func setLivreurStatus(t *testing.T, username, status string) {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(models.DeliveryPersonStatus{
|
||||
Username: username,
|
||||
Status: status,
|
||||
LastUpdate: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal DeliveryPersonStatus: %v", err)
|
||||
}
|
||||
key := "delivery:status:" + username
|
||||
if err := db.Redis.Set(db.RedisCtx, key, data, 0).Err(); err != nil {
|
||||
t.Fatalf("setLivreurStatus: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
db.Redis.Del(db.RedisCtx, key)
|
||||
})
|
||||
}
|
||||
|
||||
func setDeliveryModeSettings(t *testing.T, mode models.DeliveryModeConfig) {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(mode)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal delivery_mode: %v", err)
|
||||
}
|
||||
if err := testDB.GDB.Exec(
|
||||
`INSERT INTO app_settings (key, value) VALUES ('delivery_mode', ?)
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`,
|
||||
string(data),
|
||||
).Error; err != nil {
|
||||
t.Fatalf("setDeliveryModeSettings: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
testDB.GDB.Exec(`DELETE FROM app_settings WHERE key = 'delivery_mode'`)
|
||||
})
|
||||
}
|
||||
|
||||
func containsUsername(list []string, username string) bool {
|
||||
for _, u := range list {
|
||||
if u == username {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ── GetCommandCategories ─────────────────────────────────────────────────────
|
||||
|
||||
func TestGetCommandCategories_ReturnsDistinctProductCategories(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_categories")
|
||||
productA := newTestProductWithCategory(t, "CatA", "cat_a", 10)
|
||||
productB := newTestProductWithCategory(t, "CatB", "cat_b", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productA, 1, 10)
|
||||
testDB.GDB.Exec(
|
||||
`INSERT INTO command_items (command_id, product_id, produit, quantite, prix, status) VALUES (?, ?, 'item2', 1, 10, 'pending')`,
|
||||
cmdID, productB,
|
||||
)
|
||||
|
||||
categories, err := testDB.GetCommandCategories(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCommandCategories: %v", err)
|
||||
}
|
||||
if len(categories) != 2 || !containsUsername(categories, "cat_a") || !containsUsername(categories, "cat_b") {
|
||||
t.Errorf("catégories: got=%v want=[cat_a cat_b]", categories)
|
||||
}
|
||||
}
|
||||
|
||||
// ── GetEligibleDeliverymenForCommand ─────────────────────────────────────────
|
||||
|
||||
func TestGetEligibleDeliverymenForCommand_SingleModeReturnsAllActive(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_single")
|
||||
productID := newTestProductWithCategory(t, "Single", "cat_a", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10)
|
||||
|
||||
livreurA := testUserPrefix + "delivmode_single_a"
|
||||
livreurB := testUserPrefix + "delivmode_single_b"
|
||||
setLivreurStatus(t, livreurA, "available")
|
||||
setLivreurStatus(t, livreurB, "available")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{Mode: "single"})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if !containsUsername(eligible, livreurA) || !containsUsername(eligible, livreurB) {
|
||||
t.Errorf("mode single doit renvoyer tous les livreurs actifs: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEligibleDeliverymenForCommand_CategoryBasedFiltersToMatchingRoute(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_filter")
|
||||
productID := newTestProductWithCategory(t, "Filter", "cat_a", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10)
|
||||
|
||||
livreurA := testUserPrefix + "delivmode_filter_a"
|
||||
livreurB := testUserPrefix + "delivmode_filter_b"
|
||||
setLivreurStatus(t, livreurA, "available")
|
||||
setLivreurStatus(t, livreurB, "available")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{
|
||||
Mode: "category_based",
|
||||
CategoryRoutes: []models.CategoryRoute{
|
||||
{DeliverymanUsername: livreurA, Categories: []string{"cat_a"}},
|
||||
{DeliverymanUsername: livreurB, Categories: []string{"cat_b"}},
|
||||
},
|
||||
})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if !containsUsername(eligible, livreurA) {
|
||||
t.Errorf("livreurA (cat_a) doit être éligible: got=%v", eligible)
|
||||
}
|
||||
if containsUsername(eligible, livreurB) {
|
||||
t.Errorf("livreurB (cat_b, non commandée) ne doit pas être éligible: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
|
||||
// Cas limite documenté explicitement dans le modèle métier : une commande
|
||||
// mixte (catégories relevant de livreurs différents) doit renvoyer l'UNION
|
||||
// des livreurs éligibles, pas une intersection (aucun livreur unique ne gère
|
||||
// forcément toutes les catégories à la fois).
|
||||
func TestGetEligibleDeliverymenForCommand_MixedCategoryCommand_ReturnsUnion(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_mixed")
|
||||
productA := newTestProductWithCategory(t, "MixedA", "cat_a", 10)
|
||||
productB := newTestProductWithCategory(t, "MixedB", "cat_b", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productA, 1, 10)
|
||||
testDB.GDB.Exec(
|
||||
`INSERT INTO command_items (command_id, product_id, produit, quantite, prix, status) VALUES (?, ?, 'item2', 1, 10, 'pending')`,
|
||||
cmdID, productB,
|
||||
)
|
||||
|
||||
livreurA := testUserPrefix + "delivmode_mixed_a"
|
||||
livreurB := testUserPrefix + "delivmode_mixed_b"
|
||||
setLivreurStatus(t, livreurA, "available")
|
||||
setLivreurStatus(t, livreurB, "available")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{
|
||||
Mode: "category_based",
|
||||
CategoryRoutes: []models.CategoryRoute{
|
||||
{DeliverymanUsername: livreurA, Categories: []string{"cat_a"}},
|
||||
{DeliverymanUsername: livreurB, Categories: []string{"cat_b"}},
|
||||
},
|
||||
})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if !containsUsername(eligible, livreurA) || !containsUsername(eligible, livreurB) {
|
||||
t.Errorf("commande mixte cat_a+cat_b doit renvoyer l'union des deux livreurs: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEligibleDeliverymenForCommand_NoRouteMatchesFallsBackToAllActive(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_nomatch")
|
||||
productID := newTestProductWithCategory(t, "NoMatch", "cat_c", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10)
|
||||
|
||||
livreurA := testUserPrefix + "delivmode_nomatch_a"
|
||||
setLivreurStatus(t, livreurA, "available")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{
|
||||
Mode: "category_based",
|
||||
CategoryRoutes: []models.CategoryRoute{
|
||||
{DeliverymanUsername: livreurA, Categories: []string{"cat_a"}}, // ne couvre pas cat_c
|
||||
},
|
||||
})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if !containsUsername(eligible, livreurA) {
|
||||
t.Errorf("aucune route ne couvre cat_c -> repli sur tous les livreurs actifs: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEligibleDeliverymenForCommand_EmptyCategoryRoutesFallsBackToAllActive(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_emptyroutes")
|
||||
productID := newTestProductWithCategory(t, "EmptyRoutes", "cat_a", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10)
|
||||
|
||||
livreurA := testUserPrefix + "delivmode_emptyroutes_a"
|
||||
setLivreurStatus(t, livreurA, "available")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{Mode: "category_based", CategoryRoutes: []models.CategoryRoute{}})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if !containsUsername(eligible, livreurA) {
|
||||
t.Errorf("category_based sans route configurée -> repli sur tous les livreurs actifs: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEligibleDeliverymenForCommand_OfflineLivreurNeverEligible(t *testing.T) {
|
||||
cleanupStockTestData(t)
|
||||
username := newTestClient(t, "delivmode_offline")
|
||||
productID := newTestProductWithCategory(t, "Offline", "cat_a", 10)
|
||||
cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10)
|
||||
|
||||
onlineLivreur := testUserPrefix + "delivmode_offline_online"
|
||||
offlineLivreur := testUserPrefix + "delivmode_offline_offline"
|
||||
setLivreurStatus(t, onlineLivreur, "available")
|
||||
setLivreurStatus(t, offlineLivreur, "offline")
|
||||
setDeliveryModeSettings(t, models.DeliveryModeConfig{Mode: "single"})
|
||||
|
||||
eligible, err := testDB.GetEligibleDeliverymenForCommand(cmdID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetEligibleDeliverymenForCommand: %v", err)
|
||||
}
|
||||
if containsUsername(eligible, offlineLivreur) {
|
||||
t.Errorf("un livreur offline ne doit jamais être éligible: got=%v", eligible)
|
||||
}
|
||||
if !containsUsername(eligible, onlineLivreur) {
|
||||
t.Errorf("le livreur en ligne doit être éligible: got=%v", eligible)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user