257 lines
8 KiB
Go
257 lines
8 KiB
Go
package usecase
|
|
|
|
// shipping_usecase_test.go — Testes de unidade: cálculo de frete
|
|
//
|
|
// Cobre:
|
|
// - Frete grátis por threshold
|
|
// - Frete mínimo
|
|
// - Endereço fora do raio de entrega
|
|
// - Frete calculado por distância
|
|
// - Sem configuração de shipping (frete grátis implícito)
|
|
// - Retirada em loja disponível
|
|
//
|
|
// Execute com:
|
|
// go test ./internal/usecase/... -run TestShipping -v
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
"github.com/saveinmed/backend-go/internal/domain"
|
|
"github.com/saveinmed/backend-go/internal/infrastructure/mapbox"
|
|
"github.com/saveinmed/backend-go/internal/infrastructure/notifications"
|
|
)
|
|
|
|
func newShippingTestService() (*Service, *MockRepository) {
|
|
repo := NewMockRepository()
|
|
notify := notifications.NewLoggerNotificationService()
|
|
// Usar um cliente mapbox com token vazio — fará a requisição HTTP falhar,
|
|
// o que faz o CalculateShipping usar haversine como fallback (linha 71 de shipping_usecase.go).
|
|
// Isso evita nil pointer panic e permite testar a lógica de negócio localmente.
|
|
mb := mapbox.New("test-token-invalid")
|
|
svc := NewService(repo, nil, mb, notify, 0.05, 0.12, "test-secret", 0, "")
|
|
return svc, repo
|
|
}
|
|
|
|
// baseSettings retorna uma configuração de frete padrão para testes.
|
|
func baseShippingSettings(vendorID uuid.UUID) domain.ShippingSettings {
|
|
threshold := int64(10000) // frete grátis acima de R$100
|
|
return domain.ShippingSettings{
|
|
VendorID: vendorID,
|
|
Active: true,
|
|
MaxRadiusKm: 50.0,
|
|
PricePerKmCents: 200, // R$2/km
|
|
MinFeeCents: 1500, // R$15 mínimo
|
|
FreeShippingThresholdCents: &threshold,
|
|
Latitude: -23.55, // São Paulo centro
|
|
Longitude: -46.63,
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Happy path — Cálculo de frete
|
|
// =============================================================================
|
|
|
|
func TestShippingCalculate_FreeByThreshold(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
settings := baseShippingSettings(vendorID)
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
// Compra acima do threshold → frete grátis
|
|
buyerAddr := &domain.Address{
|
|
Latitude: -23.56, // próximo ao vendedor
|
|
Longitude: -46.64,
|
|
}
|
|
cartTotal := int64(15000) // R$150 > threshold de R$100
|
|
|
|
fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, cartTotal)
|
|
if err != nil {
|
|
t.Fatalf("CalculateShipping não deve falhar para compra acima do threshold: %v", err)
|
|
}
|
|
if fee != 0 {
|
|
t.Errorf("Frete deve ser GRÁTIS para cart_total=%d acima do threshold=%d, got fee=%d",
|
|
cartTotal, *settings.FreeShippingThresholdCents, fee)
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculate_MinFee(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
settings.FreeShippingThresholdCents = nil // sem threshold de frete grátis
|
|
settings.MaxRadiusKm = 1000 // raio gigante para não rejeitar
|
|
settings.PricePerKmCents = 1 // tarifa mínima por km
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
// Comprando muito perto — deveria pagar só o mínimo
|
|
buyerAddr := &domain.Address{
|
|
Latitude: settings.Latitude + 0.001, // ~100m de distância
|
|
Longitude: settings.Longitude,
|
|
}
|
|
|
|
fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
|
|
if err != nil {
|
|
t.Fatalf("CalculateShipping falhou: %v", err)
|
|
}
|
|
if fee < settings.MinFeeCents {
|
|
t.Errorf("Taxa calculada (%d) deve ser pelo menos o mínimo (%d)", fee, settings.MinFeeCents)
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculate_OutOfRange(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
settings.MaxRadiusKm = 5 // raio pequeno
|
|
settings.Latitude = -23.55
|
|
settings.Longitude = -46.63
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
// Comprador longe demais (Rio de Janeiro ~360km de SP)
|
|
buyerAddr := &domain.Address{
|
|
Latitude: -22.90,
|
|
Longitude: -43.17,
|
|
}
|
|
|
|
_, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
|
|
if err == nil {
|
|
t.Fatal("CalculateShipping deve retornar erro quando endereço está fora do raio de entrega")
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculate_NoSettings(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
// Não adicionar settings → frete grátis implícito
|
|
|
|
_ = repo // sem settings configurados
|
|
|
|
buyerAddr := &domain.Address{
|
|
Latitude: -23.56,
|
|
Longitude: -46.64,
|
|
}
|
|
|
|
fee, dist, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
|
|
if err != nil {
|
|
t.Fatalf("Sem configuração de frete deve retornar fee=0 sem erro: %v", err)
|
|
}
|
|
if fee != 0 {
|
|
t.Errorf("Sem configuração, fee deve ser 0, got %d", fee)
|
|
}
|
|
if dist != 0 {
|
|
t.Errorf("Sem configuração, dist deve ser 0, got %f", dist)
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculate_InactiveSettings(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
settings.Active = false // frete desativado pelo vendedor
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
buyerAddr := &domain.Address{
|
|
Latitude: -23.56,
|
|
Longitude: -46.64,
|
|
}
|
|
|
|
fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
|
|
if err != nil {
|
|
t.Fatalf("Frete inativo deve retornar fee=0 sem erro: %v", err)
|
|
}
|
|
if fee != 0 {
|
|
t.Errorf("Frete inativo deve retornar fee=0, got %d", fee)
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculate_MissingCoordinates(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
// Endereço sem coordenadas → deve usar fee mínimo
|
|
buyerAddr := &domain.Address{
|
|
Latitude: 0,
|
|
Longitude: 0,
|
|
}
|
|
|
|
fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 1000)
|
|
if err != nil {
|
|
t.Fatalf("Endereço sem coordenadas não deve retornar erro: %v", err)
|
|
}
|
|
// Sem coordenadas retorna o mínimo configurado
|
|
if fee != settings.MinFeeCents {
|
|
t.Errorf("Sem coordenadas, fee deve ser MinFeeCents=%d, got %d", settings.MinFeeCents, fee)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Opções de shipping (delivery + pickup)
|
|
// =============================================================================
|
|
|
|
func TestShippingCalculateOptions_DeliveryAndPickup(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
settings.PickupActive = true
|
|
settings.PickupAddress = "Av. Paulista, 1000 - SP"
|
|
settings.PickupHours = "08:00-18:00"
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
options, err := svc.CalculateShippingOptions(context.Background(), vendorID, -23.56, -46.64, 5000)
|
|
if err != nil {
|
|
t.Fatalf("CalculateShippingOptions falhou: %v", err)
|
|
}
|
|
|
|
hasDelivery := false
|
|
hasPickup := false
|
|
for _, opt := range options {
|
|
switch opt.Type {
|
|
case "delivery":
|
|
hasDelivery = true
|
|
case "pickup":
|
|
hasPickup = true
|
|
if opt.ValueCents != 0 {
|
|
t.Errorf("Pickup deve ter custo zero, got %d", opt.ValueCents)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasDelivery {
|
|
t.Error("Deve haver opção de entrega (delivery)")
|
|
}
|
|
if !hasPickup {
|
|
t.Error("Pickup ativo deve gerar opção de retirada")
|
|
}
|
|
}
|
|
|
|
func TestShippingCalculateOptions_PickupOnly(t *testing.T) {
|
|
svc, repo := newShippingTestService()
|
|
vendorID := uuid.Must(uuid.NewV7())
|
|
|
|
settings := baseShippingSettings(vendorID)
|
|
settings.Active = false // entrega desativada
|
|
settings.PickupActive = true
|
|
settings.PickupAddress = "Rua Teste, 123"
|
|
settings.PickupHours = "09:00-17:00"
|
|
repo.shippingSettings[vendorID] = settings
|
|
|
|
options, err := svc.CalculateShippingOptions(context.Background(), vendorID, -23.56, -46.64, 5000)
|
|
if err != nil {
|
|
t.Fatalf("CalculateShippingOptions falhou: %v", err)
|
|
}
|
|
|
|
for _, opt := range options {
|
|
if opt.Type == "delivery" {
|
|
t.Error("Entrega desativada não deve gerar opção delivery")
|
|
}
|
|
}
|
|
}
|