180 lines
6.7 KiB
Go
180 lines
6.7 KiB
Go
package tests
|
|
|
|
import (
|
|
"fmt"
|
|
"gestion/db"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const notificationsScheduledKey = "notifications:scheduled"
|
|
|
|
// ScheduleETANotifications programme un rappel 5min et 3min avant l'arrivée
|
|
// estimée — mais seulement si l'ETA le justifie (voir bornes ci-dessous).
|
|
func TestScheduleETANotifications_SchedulesBothForLongETA(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_long")
|
|
productID := newTestProduct(t, "EtaNotifLong", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
before := time.Now()
|
|
if err := testDB.ScheduleETANotifications(cmdID, 10); err != nil {
|
|
t.Fatalf("ScheduleETANotifications: %v", err)
|
|
}
|
|
|
|
score5, err := getScheduledScore(t, cmdID, "5min")
|
|
if err != nil {
|
|
t.Fatalf("notification 5min absente: %v", err)
|
|
}
|
|
score3, err := getScheduledScore(t, cmdID, "3min")
|
|
if err != nil {
|
|
t.Fatalf("notification 3min absente: %v", err)
|
|
}
|
|
|
|
wantAt5 := before.Add(5 * time.Minute).Unix() // arrivée dans 10min - 5min = dans 5min
|
|
wantAt3 := before.Add(7 * time.Minute).Unix() // arrivée dans 10min - 3min = dans 7min
|
|
if diff := abs64(score5 - wantAt5); diff > 2 {
|
|
t.Errorf("score notification 5min: got=%d want≈%d (écart %ds)", score5, wantAt5, diff)
|
|
}
|
|
if diff := abs64(score3 - wantAt3); diff > 2 {
|
|
t.Errorf("score notification 3min: got=%d want≈%d (écart %ds)", score3, wantAt3, diff)
|
|
}
|
|
if score3 <= score5 {
|
|
t.Errorf("la notification 3min doit être programmée après la 5min (plus proche de l'arrivée): score5=%d score3=%d", score5, score3)
|
|
}
|
|
}
|
|
|
|
// À la limite exacte (ETA = 5 min), un rappel "5 minutes avant l'arrivée"
|
|
// se déclencherait immédiatement (redondant) — il n'est donc volontairement
|
|
// pas programmé (condition stricte ">5", pas ">=5"). Seul le rappel 3min
|
|
// reste pertinent.
|
|
func TestScheduleETANotifications_ExactlyFiveMinutes_SkipsFiveMinReminder(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_five")
|
|
productID := newTestProduct(t, "EtaNotifFive", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
if err := testDB.ScheduleETANotifications(cmdID, 5); err != nil {
|
|
t.Fatalf("ScheduleETANotifications: %v", err)
|
|
}
|
|
|
|
if _, err := getScheduledScore(t, cmdID, "5min"); err == nil {
|
|
t.Error("aucune notification 5min ne doit être programmée quand ETA=5min exactement")
|
|
}
|
|
if _, err := getScheduledScore(t, cmdID, "3min"); err != nil {
|
|
t.Errorf("la notification 3min doit être programmée quand ETA=5min: %v", err)
|
|
}
|
|
}
|
|
|
|
// À ETA = 3 min, même le rappel "3 minutes avant" serait immédiat : aucune
|
|
// notification ne doit être programmée.
|
|
func TestScheduleETANotifications_ExactlyThreeMinutes_SchedulesNothing(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_three")
|
|
productID := newTestProduct(t, "EtaNotifThree", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
if err := testDB.ScheduleETANotifications(cmdID, 3); err != nil {
|
|
t.Fatalf("ScheduleETANotifications: %v", err)
|
|
}
|
|
|
|
if _, err := getScheduledScore(t, cmdID, "5min"); err == nil {
|
|
t.Error("aucune notification 5min ne doit être programmée pour ETA=3min")
|
|
}
|
|
if _, err := getScheduledScore(t, cmdID, "3min"); err == nil {
|
|
t.Error("aucune notification 3min ne doit être programmée pour ETA=3min (serait immédiate)")
|
|
}
|
|
}
|
|
|
|
// Une ETA très courte (MinETA=3min, plancher de tout le système) ne doit
|
|
// jamais programmer de notification de rappel — cohérent avec le cas ci-dessus.
|
|
func TestScheduleETANotifications_VeryShortETASchedulesNothing(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_short")
|
|
productID := newTestProduct(t, "EtaNotifShort", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
if err := testDB.ScheduleETANotifications(cmdID, 1); err != nil {
|
|
t.Fatalf("ScheduleETANotifications: %v", err)
|
|
}
|
|
if _, err := getScheduledScore(t, cmdID, "5min"); err == nil {
|
|
t.Error("aucune notification ne doit être programmée pour une ETA d'1 minute")
|
|
}
|
|
if _, err := getScheduledScore(t, cmdID, "3min"); err == nil {
|
|
t.Error("aucune notification ne doit être programmée pour une ETA d'1 minute")
|
|
}
|
|
}
|
|
|
|
// SetCommandETA (appelée par UpdateDeliveryStatus au passage en "en_route")
|
|
// déclenche automatiquement ScheduleETANotifications — vérifie l'intégration
|
|
// complète, pas seulement la fonction isolée.
|
|
func TestSetCommandETA_TriggersScheduledNotifications(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_integration")
|
|
productID := newTestProduct(t, "EtaNotifIntegration", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETA(cmdID, 15); err != nil {
|
|
t.Fatalf("SetCommandETA: %v", err)
|
|
}
|
|
|
|
if _, err := getScheduledScore(t, cmdID, "5min"); err != nil {
|
|
t.Errorf("SetCommandETA doit programmer une notification 5min: %v", err)
|
|
}
|
|
if _, err := getScheduledScore(t, cmdID, "3min"); err != nil {
|
|
t.Errorf("SetCommandETA doit programmer une notification 3min: %v", err)
|
|
}
|
|
}
|
|
|
|
// Le message envoyé au client doit contenir une indication de temps lisible,
|
|
// pas juste le statut brut.
|
|
func TestSendETANotification_MessageContainsReadableTime(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_notif_message")
|
|
productID := newTestProduct(t, "EtaNotifMessage", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", "", productID, 1, 10)
|
|
|
|
channel := fmt.Sprintf("notifications:command:%d", cmdID)
|
|
pubsub := db.Redis.Subscribe(db.RedisCtx, channel)
|
|
defer pubsub.Close()
|
|
// Consommer le message de confirmation d'abonnement avant de publier.
|
|
if _, err := pubsub.Receive(db.RedisCtx); err != nil {
|
|
t.Fatalf("abonnement pubsub: %v", err)
|
|
}
|
|
|
|
testDB.SendETANotification(cmdID, "5min")
|
|
|
|
select {
|
|
case msg := <-pubsub.Channel():
|
|
if msg.Payload == "" {
|
|
t.Fatal("message de notification vide")
|
|
}
|
|
if !strings.Contains(msg.Payload, "5min") {
|
|
t.Errorf("le message doit indiquer le temps restant (%q): %q", "5min", msg.Payload)
|
|
}
|
|
t.Logf("message reçu: %q", msg.Payload)
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("aucun message reçu sur le canal de notification dans le délai imparti")
|
|
}
|
|
}
|
|
|
|
func abs64(n int64) int64 {
|
|
if n < 0 {
|
|
return -n
|
|
}
|
|
return n
|
|
}
|
|
|
|
// getScheduledScore lit le score (timestamp Unix) d'une notification
|
|
// programmée pour "{commandID}:{suffix}" dans le sorted set Redis.
|
|
func getScheduledScore(t *testing.T, commandID int, suffix string) (int64, error) {
|
|
t.Helper()
|
|
member := fmt.Sprintf("%d:%s", commandID, suffix)
|
|
score, err := db.Redis.ZScore(db.RedisCtx, notificationsScheduledKey, member).Result()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int64(score), nil
|
|
}
|