209 lines
7.6 KiB
Go
209 lines
7.6 KiB
Go
package tests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"gestion/handlers"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Les clients ont signalé ne jamais voir le temps de livraison (notifications,
|
|
// app mobile, site web). Ces tests reproduisent le chemin réel : une commande
|
|
// est assignée automatiquement (le worker de queue appelle
|
|
// SetCommandETAWithDetails, PAS SetCommandETA), puis un client consulte le
|
|
// suivi de sa commande — exactement ce que fait l'app mobile / le site web.
|
|
|
|
func etaTestContext(username string, commandID int) (*gin.Context, *httptest.ResponseRecorder) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/commands/x/tracking", nil)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = req
|
|
c.Set("database", testDB)
|
|
c.Set("username", username)
|
|
c.Set("role", "client")
|
|
c.Params = gin.Params{{Key: "id", Value: strconv.Itoa(commandID)}}
|
|
return c, rec
|
|
}
|
|
|
|
// SetCommandETAWithDetails est le chemin utilisé par le worker
|
|
// d'auto-assignation/réoptimisation de queue (db/redis_queue_optimization.go,
|
|
// db/redis_queue_assignment.go) — de loin le plus emprunté en production.
|
|
// Il doit remplir "eta_minutes", pas seulement "total_eta_minutes", car
|
|
// c'est le champ que l'app mobile et le site web lisent (confirmé dans
|
|
// mobile/src/screens/client/OrderTrackingScreen.tsx et api.ts des deux
|
|
// frontends).
|
|
func TestSetCommandETAWithDetails_WritesEtaMinutesField(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_details_field")
|
|
productID := newTestProduct(t, "EtaDetailsField", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", testUserPrefix+"livreurETA", productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETAWithDetails(cmdID, 22, 1); err != nil {
|
|
t.Fatalf("SetCommandETAWithDetails: %v", err)
|
|
}
|
|
|
|
etaData, err := testDB.GetCommandETA(cmdID)
|
|
if err != nil {
|
|
t.Fatalf("GetCommandETA: %v", err)
|
|
}
|
|
|
|
got, ok := etaData["eta_minutes"]
|
|
if !ok || got == "" {
|
|
t.Errorf(`champ "eta_minutes" absent après SetCommandETAWithDetails (contenu: %v) — `+
|
|
`c'est le champ lu par le mobile et le site web, d'où l'absence de temps affiché`, etaData)
|
|
} else if got != "22" {
|
|
t.Errorf(`"eta_minutes" = %q, want "22"`, got)
|
|
}
|
|
}
|
|
|
|
// Reproduction bout-en-bout du symptôme signalé : après une assignation
|
|
// automatique (SetCommandETAWithDetails), le client consulte le suivi de sa
|
|
// commande (GetCommandTracking, l'endpoint utilisé par l'app mobile et le
|
|
// site web) — la réponse doit exposer eta.eta_minutes.
|
|
func TestGetCommandTracking_ExposesEtaMinutesAfterAutoAssignment(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_tracking_client")
|
|
productID := newTestProduct(t, "EtaTrackingClient", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", testUserPrefix+"livreurETA2", productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETAWithDetails(cmdID, 17, 2); err != nil {
|
|
t.Fatalf("SetCommandETAWithDetails: %v", err)
|
|
}
|
|
|
|
c, rec := etaTestContext(username, cmdID)
|
|
handlers.GetCommandTracking(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
Success bool `json:"success"`
|
|
ETA map[string]any `json:"eta"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("décodage réponse: %v body=%s", err, rec.Body.String())
|
|
}
|
|
|
|
etaMinutesRaw, ok := resp.ETA["eta_minutes"]
|
|
if !ok {
|
|
t.Fatalf(`la réponse de /tracking n'expose pas "eta.eta_minutes" (contenu eta: %v) — `+
|
|
`reproduit exactement le bug signalé par les clients`, resp.ETA)
|
|
}
|
|
etaMinutesStr, _ := etaMinutesRaw.(string)
|
|
if got, _ := strconv.Atoi(etaMinutesStr); got != 17 {
|
|
t.Errorf("eta.eta_minutes: got=%v want=17", etaMinutesRaw)
|
|
}
|
|
}
|
|
|
|
// Même reproduction via GetCommandStatus (autre endpoint de suivi, utilisé
|
|
// par l'app mobile pour le statut temps réel).
|
|
func TestGetCommandStatus_ExposesEtaMinutesAfterAutoAssignment(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_status_client")
|
|
productID := newTestProduct(t, "EtaStatusClient", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", testUserPrefix+"livreurETA3", productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETAWithDetails(cmdID, 9, 1); err != nil {
|
|
t.Fatalf("SetCommandETAWithDetails: %v", err)
|
|
}
|
|
|
|
c, rec := etaTestContext(username, cmdID)
|
|
handlers.GetCommandStatus(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
ETA map[string]any `json:"eta"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("décodage réponse: %v", err)
|
|
}
|
|
if _, ok := resp.ETA["eta_minutes"]; !ok {
|
|
t.Fatalf(`GetCommandStatus n'expose pas "eta.eta_minutes" (contenu: %v)`, resp.ETA)
|
|
}
|
|
}
|
|
|
|
// SetCommandETA (chemin utilisé par UpdateDeliveryStatus côté livreur) doit
|
|
// lui aussi rester lisible par les lecteurs qui attendent "total_eta_minutes"
|
|
// (handlers/deleviry.go, validation_deleviry.go, geoloca.go).
|
|
func TestSetCommandETA_AlsoWritesTotalEtaMinutesField(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_simple_field")
|
|
productID := newTestProduct(t, "EtaSimpleField", 10)
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", testUserPrefix+"livreurETA4", productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETA(cmdID, 14); err != nil {
|
|
t.Fatalf("SetCommandETA: %v", err)
|
|
}
|
|
|
|
etaData, err := testDB.GetCommandETA(cmdID)
|
|
if err != nil {
|
|
t.Fatalf("GetCommandETA: %v", err)
|
|
}
|
|
if got, ok := etaData["total_eta_minutes"]; !ok || got != "14" {
|
|
t.Errorf(`"total_eta_minutes" = %q (présent=%v), want "14"`, got, ok)
|
|
}
|
|
if got, ok := etaData["eta_minutes"]; !ok || got != "14" {
|
|
t.Errorf(`"eta_minutes" = %q (présent=%v), want "14"`, got, ok)
|
|
}
|
|
}
|
|
|
|
// GetDeliverymanLocationForCommand (vue admin/cabine) lisait l'ETA via
|
|
// Redis.Get sur une clé qui est en réalité un hash (HSet) — l'erreur
|
|
// WRONGTYPE était silencieusement ignorée et etaMinutes restait toujours à 0.
|
|
func TestGetDeliverymanLocationForCommand_ExposesEtaMinutes(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
username := newTestClient(t, "eta_admin_view_client")
|
|
productID := newTestProduct(t, "EtaAdminView", 10)
|
|
livreurUsername := testUserPrefix + "eta_admin_view_livreur"
|
|
cmdID := newTestCommandWithItem(t, username, "en_route", livreurUsername, productID, 1, 10)
|
|
|
|
if err := testDB.SetCommandETAWithDetails(cmdID, 12, 1); err != nil {
|
|
t.Fatalf("SetCommandETAWithDetails: %v", err)
|
|
}
|
|
// Position GPS du livreur, requise par le handler avant de lire l'ETA.
|
|
if err := testDB.UpdateDeliveryPersonLocation(livreurUsername, 47.2148, -1.5584); err != nil {
|
|
t.Fatalf("UpdateDeliveryPersonLocation: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/x", nil)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = req
|
|
c.Set("database", testDB)
|
|
c.Set("username", "admin_test")
|
|
c.Set("role", "admin")
|
|
c.Params = gin.Params{{Key: "id", Value: strconv.Itoa(cmdID)}}
|
|
|
|
handlers.GetDeliverymanLocationForCommand(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
Data struct {
|
|
ETA struct {
|
|
Minutes float64 `json:"minutes"`
|
|
HasETA bool `json:"has_eta"`
|
|
} `json:"eta"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("décodage réponse: %v body=%s", err, rec.Body.String())
|
|
}
|
|
if !resp.Data.ETA.HasETA {
|
|
t.Errorf("has_eta devrait être true, ETA pourtant définie via SetCommandETAWithDetails")
|
|
}
|
|
if resp.Data.ETA.Minutes != 12 {
|
|
t.Errorf("data.eta.minutes: got=%.0f want=12", resp.Data.ETA.Minutes)
|
|
}
|
|
}
|