189 lines
7.9 KiB
Go
189 lines
7.9 KiB
Go
package tests
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gestion/handlers"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// UpdateDeliveryStatus (handlers/deleviry.go) refuse de valider une livraison
|
|
// (statut "livre") si le livreur se trouve à plus de 350m de la destination
|
|
// (contrôle anti-fraude — seuil relevé de 100m à 350m à la demande explicite,
|
|
// pour tolérer l'imprécision GPS réelle en zone urbaine/immeuble).
|
|
|
|
const earthRadiusMeters = 6371000.0
|
|
|
|
// destinationPointNorthOf renvoie un point situé à "meters" au nord de
|
|
// (lat, lon) — même longitude, donc distance ≈ purement le delta de latitude
|
|
// (formule identique à utils.CalculateDistance pour ce cas particulier).
|
|
func destinationPointNorthOf(lat, lon, meters float64) (float64, float64) {
|
|
latOffsetRad := meters / earthRadiusMeters
|
|
latOffsetDeg := latOffsetRad * (180 / math.Pi)
|
|
return lat + latOffsetDeg, lon
|
|
}
|
|
|
|
func setCommandDestination(t *testing.T, commandID int, lat, lon float64) {
|
|
t.Helper()
|
|
if err := testDB.GDB.Exec(
|
|
`UPDATE commandes SET dest_latitude = ?, dest_longitude = ? WHERE id = ?`,
|
|
lat, lon, commandID,
|
|
).Error; err != nil {
|
|
t.Fatalf("setCommandDestination: %v", err)
|
|
}
|
|
}
|
|
|
|
// deliveryStatusContextJSON est la variante de deliveryStatusContext (voir
|
|
// penalty_test.go) qui accepte un corps JSON arbitraire — nécessaire ici pour
|
|
// pouvoir passer latitude/longitude, que l'helper existant ne supporte pas.
|
|
func deliveryStatusContextJSON(username string, commandID int, body []byte) (*gin.Context, *httptest.ResponseRecorder) {
|
|
req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/livreur/deliveries/%d/status", commandID), bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = req
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", commandID)}}
|
|
c.Set("database", testDB)
|
|
c.Set("username", username)
|
|
c.Set("role", "livreur")
|
|
return c, rec
|
|
}
|
|
|
|
const nantesLat, nantesLon = 47.2184, -1.5536
|
|
|
|
func TestUpdateDeliveryStatus_GPS_WithinThresholdValidatesDelivery(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
livreur := newTestClient(t, "gps_livreur_within")
|
|
client := newTestClient(t, "gps_client_within")
|
|
productID := newTestProduct(t, "GPSWithin", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", livreur, productID, 1, 10)
|
|
setCommandDestination(t, cmdID, nantesLat, nantesLon)
|
|
|
|
livreurLat, livreurLon := destinationPointNorthOf(nantesLat, nantesLon, 200) // 200m < 350m
|
|
body, _ := json.Marshal(map[string]any{"status": "livre", "latitude": livreurLat, "longitude": livreurLon})
|
|
c, rec := deliveryStatusContextJSON(livreur, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "livre" {
|
|
t.Errorf("statut après validation à 200m: got=%s want=livre", got)
|
|
}
|
|
}
|
|
|
|
func TestUpdateDeliveryStatus_GPS_BeyondThresholdRejectsValidation(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
livreur := newTestClient(t, "gps_livreur_beyond")
|
|
client := newTestClient(t, "gps_client_beyond")
|
|
productID := newTestProduct(t, "GPSBeyond", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", livreur, productID, 1, 10)
|
|
setCommandDestination(t, cmdID, nantesLat, nantesLon)
|
|
|
|
livreurLat, livreurLon := destinationPointNorthOf(nantesLat, nantesLon, 400) // 400m > 350m
|
|
body, _ := json.Marshal(map[string]any{"status": "livre", "latitude": livreurLat, "longitude": livreurLon})
|
|
c, rec := deliveryStatusContextJSON(livreur, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status HTTP: got=%d want=%d body=%s", rec.Code, http.StatusBadRequest, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "en_route" {
|
|
t.Errorf("le statut ne doit pas passer à 'livre' au-delà de 350m: got=%s want=en_route", got)
|
|
}
|
|
}
|
|
|
|
// Preuve directe du changement demandé : une distance de 150m, qui aurait
|
|
// échoué sous l'ancien seuil de 100m, doit maintenant réussir sous 350m.
|
|
func TestUpdateDeliveryStatus_GPS_150Meters_PassesUnderNewThreshold(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
livreur := newTestClient(t, "gps_livreur_150m")
|
|
client := newTestClient(t, "gps_client_150m")
|
|
productID := newTestProduct(t, "GPS150m", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", livreur, productID, 1, 10)
|
|
setCommandDestination(t, cmdID, nantesLat, nantesLon)
|
|
|
|
livreurLat, livreurLon := destinationPointNorthOf(nantesLat, nantesLon, 150)
|
|
body, _ := json.Marshal(map[string]any{"status": "livre", "latitude": livreurLat, "longitude": livreurLon})
|
|
c, rec := deliveryStatusContextJSON(livreur, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("150m doit être accepté sous le nouveau seuil de 350m: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "livre" {
|
|
t.Errorf("statut après validation à 150m: got=%s want=livre", got)
|
|
}
|
|
}
|
|
|
|
func TestUpdateDeliveryStatus_GPS_MissingCoordinatesRejected(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
livreur := newTestClient(t, "gps_livreur_missing_coords")
|
|
client := newTestClient(t, "gps_client_missing_coords")
|
|
productID := newTestProduct(t, "GPSMissingCoords", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", livreur, productID, 1, 10)
|
|
setCommandDestination(t, cmdID, nantesLat, nantesLon)
|
|
|
|
body, _ := json.Marshal(map[string]any{"status": "livre"}) // latitude/longitude absents (zéro)
|
|
c, rec := deliveryStatusContextJSON(livreur, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status HTTP sans coordonnées: got=%d want=%d body=%s", rec.Code, http.StatusBadRequest, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "en_route" {
|
|
t.Errorf("le statut ne doit pas changer sans coordonnées GPS: got=%s want=en_route", got)
|
|
}
|
|
}
|
|
|
|
// Si la commande n'a pas de coordonnées de destination enregistrées (adresse
|
|
// non géocodée), la validation GPS est ignorée plutôt que de bloquer le
|
|
// livreur indéfiniment.
|
|
func TestUpdateDeliveryStatus_GPS_MissingDestinationCoordinatesSkipsValidation(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
livreur := newTestClient(t, "gps_livreur_no_dest")
|
|
client := newTestClient(t, "gps_client_no_dest")
|
|
productID := newTestProduct(t, "GPSNoDest", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", livreur, productID, 1, 10)
|
|
// Pas d'appel à setCommandDestination : dest_latitude/dest_longitude restent à 0/NULL.
|
|
|
|
body, _ := json.Marshal(map[string]any{"status": "livre", "latitude": 48.8566, "longitude": 2.3522}) // Paris, sans rapport
|
|
c, rec := deliveryStatusContextJSON(livreur, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("sans destination enregistrée, la validation GPS doit être ignorée: got=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "livre" {
|
|
t.Errorf("statut: got=%s want=livre", got)
|
|
}
|
|
}
|
|
|
|
func TestUpdateDeliveryStatus_RejectsWhenNotAssignedToThisLivreur(t *testing.T) {
|
|
cleanupStockTestData(t)
|
|
assignedLivreur := newTestClient(t, "gps_assigned_livreur")
|
|
intruder := newTestClient(t, "gps_intruder_livreur")
|
|
client := newTestClient(t, "gps_client_wrong_livreur")
|
|
productID := newTestProduct(t, "GPSWrongLivreur", 10)
|
|
cmdID := newTestCommandWithItem(t, client, "en_route", assignedLivreur, productID, 1, 10)
|
|
setCommandDestination(t, cmdID, nantesLat, nantesLon)
|
|
|
|
body, _ := json.Marshal(map[string]any{"status": "livre", "latitude": nantesLat, "longitude": nantesLon})
|
|
c, rec := deliveryStatusContextJSON(intruder, cmdID, body)
|
|
handlers.UpdateDeliveryStatus(c)
|
|
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("un livreur non assigné doit être rejeté: got=%d want=%d body=%s", rec.Code, http.StatusForbidden, rec.Body.String())
|
|
}
|
|
if got := commandStatus(t, cmdID); got != "en_route" {
|
|
t.Errorf("le statut ne doit pas changer: got=%s want=en_route", got)
|
|
}
|
|
}
|