262 lines
9.9 KiB
Go
262 lines
9.9 KiB
Go
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)
|
|
}
|
|
}
|