package tests import ( "fmt" "strconv" "testing" ) // Adresses réalistes de Nantes (44000) utilisées pour vérifier que l'adresse // de livraison survit intacte à la création puis à toutes les voies de // récupération d'une commande (client, admin, livreur, historique). var nantesAddresses = []string{ "12 Rue Crébillon, 44000 Nantes", "3 Place Royale, 44000 Nantes", "5 Cours des 50 Otages, 44000 Nantes", "8 Rue de Verdun, 44000 Nantes", } // newTestCommandWithAddress crée directement une commande avec une adresse et // un statut contrôlés (en contournant le checkout), pour tester isolément la // récupération de l'adresse par les différentes fonctions de listing. func newTestCommandWithAddress(t *testing.T, username, status, address, livreurAssign string, productID int, quantite, prix float64) int { t.Helper() var cmdID int if err := testDB.GDB.Raw( `INSERT INTO commandes (username, status, adresse, livreur_assign, total_prix, created_at, updated_at) VALUES (?, ?, ?, NULLIF(?, ''), ?, NOW(), NOW()) RETURNING id`, username, status, address, livreurAssign, prix, ).Scan(&cmdID).Error; err != nil { t.Fatalf("création commande test avec adresse: %v", err) } if err := testDB.GDB.Exec( `INSERT INTO command_items (command_id, product_id, produit, quantite, prix, status) VALUES (?, ?, 'item test', ?, ?, 'pending')`, cmdID, productID, quantite, prix, ).Error; err != nil { t.Fatalf("création item test: %v", err) } return cmdID } // Le checkout réel (panier -> CreateCommandWithAddress) doit stocker l'adresse // telle quelle, et GetCommandByID doit la restituer à l'identique. func TestCreateCommandWithAddress_RoundTripsRealNantesAddress(t *testing.T) { for _, address := range nantesAddresses { t.Run(address, func(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_checkout") productID := newTestProduct(t, "AddrCheckout", 10) if _, err := testDB.AddToBasket(username, productID, 1); err != nil { t.Fatalf("AddToBasket: %v", err) } cmd, err := testDB.CreateCommandWithAddress(username, address) if err != nil { t.Fatalf("CreateCommandWithAddress: %v", err) } command, err := testDB.GetCommandByID(cmd.ID) if err != nil { t.Fatalf("GetCommandByID: %v", err) } got, _ := command["adresse"].(string) if got != address { t.Errorf("adresse récupérée: got=%q want=%q", got, address) } if got == "" { t.Error("l'adresse ne doit jamais être vide") } }) } } // Une adresse vide ou uniquement composée d'espaces doit être rejetée au // checkout — pas de commande créée avec une adresse de livraison absente. func TestCreateCommandWithAddress_RejectsEmptyOrBlankAddress(t *testing.T) { for _, address := range []string{"", " ", "\t\n"} { cleanupStockTestData(t) username := newTestClient(t, "addr_blank") productID := newTestProduct(t, "AddrBlank", 10) if _, err := testDB.AddToBasket(username, productID, 1); err != nil { t.Fatalf("AddToBasket: %v", err) } if _, err := testDB.CreateCommandWithAddress(username, address); err == nil { t.Errorf("adresse %q aurait dû être rejetée", address) } var count int64 testDB.GDB.Raw(`SELECT COUNT(*) FROM commandes WHERE username = ?`, username).Scan(&count) if count != 0 { t.Errorf("aucune commande ne doit être créée avec une adresse %q: got=%d", address, count) } } } // GetAllCommands (vue admin) doit toujours renvoyer l'adresse de chaque // commande, quel que soit son statut. func TestGetAllCommands_AlwaysIncludesAddress(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_admin_list") productID := newTestProduct(t, "AddrAdminList", 10) want := map[int]string{} for i, address := range nantesAddresses { status := []string{"pending", "assigned", "en_route", "livre"}[i%4] cmdID := newTestCommandWithAddress(t, username, status, address, "", productID, 1, 10) want[cmdID] = address } commands, err := testDB.GetAllCommands("", username) if err != nil { t.Fatalf("GetAllCommands: %v", err) } if len(commands) != len(want) { t.Fatalf("nombre de commandes: got=%d want=%d", len(commands), len(want)) } for _, c := range commands { id, _ := c["id"].(int) address, _ := c["adresse"].(string) if address == "" { t.Errorf("commande %d: adresse vide", id) } if want[id] != address { t.Errorf("commande %d: adresse=%q want=%q", id, address, want[id]) } } } // GetDeliveryPersonCommands (vue livreur) doit inclure l'adresse de chaque // commande qui lui est assignée. func TestGetDeliveryPersonCommands_IncludesAddress(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_livreur_client") productID := newTestProduct(t, "AddrLivreur", 10) livreurUsername := testUserPrefix + "addr_livreur" address := nantesAddresses[0] cmdID := newTestCommandWithAddress(t, username, "assigned", address, livreurUsername, productID, 1, 10) commands, err := testDB.GetDeliveryPersonCommands(livreurUsername, "") if err != nil { t.Fatalf("GetDeliveryPersonCommands: %v", err) } if len(commands) != 1 { t.Fatalf("nombre de commandes assignées: got=%d want=1", len(commands)) } got, _ := commands[0]["adresse"].(string) if got != address { t.Errorf("adresse: got=%q want=%q", got, address) } // Le type Go concret de la colonne "id" issue d'un Raw(...).Scan(&[]map[string]any) // n'est pas garanti (int64 selon le driver) — même précaution que le code // de production (ex: handlers/deleviry.go, fmt.Sprintf + strconv.Atoi). id, _ := strconv.Atoi(fmt.Sprintf("%v", commands[0]["id"])) if id != cmdID { t.Errorf("id de commande inattendu: got=%d want=%d", id, cmdID) } } // GetCancelledCommands doit inclure l'adresse même pour une commande annulée. func TestGetCancelledCommands_IncludesAddress(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_cancelled") productID := newTestProduct(t, "AddrCancelled", 10) address := nantesAddresses[1] newTestCommandWithAddress(t, username, "cancelled", address, "", productID, 1, 10) commands, err := testDB.GetCancelledCommands(username, 10) if err != nil { t.Fatalf("GetCancelledCommands: %v", err) } if len(commands) != 1 { t.Fatalf("nombre de commandes annulées: got=%d want=1", len(commands)) } got, _ := commands[0]["adresse"].(string) if got != address { t.Errorf("adresse: got=%q want=%q", got, address) } } // GetCompletedCommandsByUsername (historique client) doit inclure l'adresse // des commandes terminées (approved). func TestGetCompletedCommandsByUsername_IncludesAddress(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_history") productID := newTestProduct(t, "AddrHistory", 10) address := nantesAddresses[2] newTestCommandWithAddress(t, username, "approved", address, "", productID, 1, 10) commands, err := testDB.GetCompletedCommandsByUsername(username) if err != nil { t.Fatalf("GetCompletedCommandsByUsername: %v", err) } if len(commands) != 1 { t.Fatalf("nombre de commandes terminées: got=%d want=1", len(commands)) } got, _ := commands[0]["adresse"].(string) if got != address { t.Errorf("adresse: got=%q want=%q", got, address) } } // GetAllCommandsOldestFirst (vue cabine/admin triée) doit également inclure // l'adresse de chaque commande. func TestGetAllCommandsOldestFirst_IncludesAddress(t *testing.T) { cleanupStockTestData(t) username := newTestClient(t, "addr_oldest_first") productID := newTestProduct(t, "AddrOldestFirst", 10) address := nantesAddresses[3] newTestCommandWithAddress(t, username, "pending", address, "", productID, 1, 10) commands, err := testDB.GetAllCommandsOldestFirst("", username) if err != nil { t.Fatalf("GetAllCommandsOldestFirst: %v", err) } if len(commands) != 1 { t.Fatalf("nombre de commandes: got=%d want=1", len(commands)) } got, _ := commands[0]["adresse"].(string) if got != address { t.Errorf("adresse: got=%q want=%q", got, address) } }