From f371cd0874ee9cead476a6325bcf5eb50bec0e35 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 27 Sep 2026 12:30:20 +0200 Subject: [PATCH] chore: build --- backend/gestion/handlers/commands.go | 20 +++- .../address_proposal_livreur_notif_test.go | 107 ++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 backend/gestion/tests/address_proposal_livreur_notif_test.go diff --git a/backend/gestion/handlers/commands.go b/backend/gestion/handlers/commands.go index 86f15024..df4fcfa0 100644 --- a/backend/gestion/handlers/commands.go +++ b/backend/gestion/handlers/commands.go @@ -290,11 +290,16 @@ func RespondToAddressProposal(c *gin.Context) { // La colonne proposed_address est vidée par RespondToAddressProposal dès // qu'elle est traitée : on la lit avant l'appel pour pouvoir regéocoder la - // nouvelle adresse en cas d'acceptation. + // nouvelle adresse en cas d'acceptation. On récupère aussi le livreur + // assigné : ProposeAddressChange autorise un changement d'adresse même en + // "en_route" (voir commands.go ~L236-240), donc un livreur peut déjà être + // en train de rouler vers l'ancienne adresse au moment de l'acceptation. var proposedAddress string + var livreurAssign string if req.Accepted { if command, err := database.GetCommandByID(commandID); err == nil { proposedAddress, _ = command["proposed_address"].(string) + livreurAssign, _ = command["livreur_assign"].(string) } } @@ -305,6 +310,19 @@ func RespondToAddressProposal(c *gin.Context) { if req.Accepted && proposedAddress != "" { updateCommandDestinationCoords(database, geoService, commandID, proposedAddress) + + // L'ETA en cache correspond au trajet vers l'ancienne destination — + // on l'invalide pour forcer un recalcul sur les nouvelles coordonnées. + db.Redis.Del(db.RedisCtx, fmt.Sprintf("command:eta:%d", commandID)) + + // Sans cette notification, le livreur déjà en route continuerait à + // naviguer vers l'ancienne adresse sans savoir que la destination a + // changé en base. + if livreurAssign != "" { + clientOrderID := database.GetClientOrderID(commandID) + livreurMsg := fmt.Sprintf("⚠️ Le client a changé l'adresse de livraison de la commande #%d : %s. Merci de mettre à jour votre itinéraire.", clientOrderID, proposedAddress) + database.NotifyLivreur(livreurAssign, commandID, "address_changed", livreurMsg) + } } action := "refusée" diff --git a/backend/gestion/tests/address_proposal_livreur_notif_test.go b/backend/gestion/tests/address_proposal_livreur_notif_test.go new file mode 100644 index 00000000..ed95360a --- /dev/null +++ b/backend/gestion/tests/address_proposal_livreur_notif_test.go @@ -0,0 +1,107 @@ +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "gestion/db" + "gestion/handlers" + + "github.com/gin-gonic/gin" +) + +// Si le livreur est déjà en route quand le client accepte une proposition +// d'adresse (ProposeAddressChange autorise ce cas même en "en_route" — voir +// commands.go ~L236-240), il doit être notifié que la destination a changé, +// sans quoi il continuerait à naviguer vers l'ancienne adresse alors que la +// nouvelle est déjà enregistrée en base (voir RespondToAddressProposal). +func TestRespondToAddressProposal_NotifiesLivreurWhenAlreadyEnRoute(t *testing.T) { + if testing.Short() { + t.Skip("appelle le vrai service Nominatim en réseau — sauté en mode -short") + } + cleanupStockTestData(t) + username := newTestClient(t, "respond_addr_notif_client") + livreur := newTestClient(t, "respond_addr_notif_livreur") + productID := newTestProduct(t, "RespondAddrNotif", 10) + cmdID := newTestCommandWithItem(t, username, "en_route", livreur, productID, 1, 10) + + if err := testDB.ProposeAddressChange(cmdID, "12 rue Crebillon, 44000 Nantes", "admin_test"); err != nil { + t.Fatalf("ProposeAddressChange: %v", err) + } + + notifKey := fmt.Sprintf("notifications:%s", livreur) + db.Redis.Del(db.RedisCtx, notifKey) + + body := []byte(`{"accepted":true}`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/commands/address/respond", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + c, _ := gin.CreateTestContext(rec) + c.Request = req + c.Set("database", testDB) + c.Set("geoService", ensureTestGeoService()) + c.Set("username", username) + c.Set("role", "client") + c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", cmdID)}} + + handlers.RespondToAddressProposal(c) + if rec.Code != http.StatusOK { + t.Fatalf("RespondToAddressProposal doit réussir: got=%d body=%s", rec.Code, rec.Body.String()) + } + + entries, err := db.Redis.LRange(db.RedisCtx, notifKey, 0, -1).Result() + if err != nil { + t.Fatalf("lecture notifications livreur: %v", err) + } + var found bool + for _, raw := range entries { + var notif struct { + CommandID int `json:"command_id"` + Type string `json:"type"` + } + if err := json.Unmarshal([]byte(raw), ¬if); err == nil && notif.CommandID == cmdID && notif.Type == "address_changed" { + found = true + break + } + } + if !found { + t.Errorf("le livreur déjà en route doit être notifié du changement d'adresse (type=address_changed, command_id=%d): notifications=%v", cmdID, entries) + } +} + +// Si aucun livreur n'est assigné, il ne doit évidemment pas y avoir de +// notification livreur envoyée (pas de panique/erreur non plus). +func TestRespondToAddressProposal_NoLivreurNotificationWhenNoneAssigned(t *testing.T) { + if testing.Short() { + t.Skip("appelle le vrai service Nominatim en réseau — sauté en mode -short") + } + cleanupStockTestData(t) + username := newTestClient(t, "respond_addr_notif_nolivreur") + productID := newTestProduct(t, "RespondAddrNotifNoLivreur", 10) + cmdID := newTestCommandWithItem(t, username, "pending", "", productID, 1, 10) + + if err := testDB.ProposeAddressChange(cmdID, "12 rue Crebillon, 44000 Nantes", "admin_test"); err != nil { + t.Fatalf("ProposeAddressChange: %v", err) + } + + body := []byte(`{"accepted":true}`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/commands/address/respond", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + c, _ := gin.CreateTestContext(rec) + c.Request = req + c.Set("database", testDB) + c.Set("geoService", ensureTestGeoService()) + c.Set("username", username) + c.Set("role", "client") + c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", cmdID)}} + + handlers.RespondToAddressProposal(c) + if rec.Code != http.StatusOK { + t.Fatalf("RespondToAddressProposal doit réussir: got=%d body=%s", rec.Code, rec.Body.String()) + } +}