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()) _ = repo.CreateCompany(context.Background(), &domain.Company{ ID: vendorID, CorporateName: "Farmacia Central", Latitude: -23.55, Longitude: -46.63, }) 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()) _ = repo.CreateCompany(context.Background(), &domain.Company{ ID: vendorID, CorporateName: "Farmacia Central", Latitude: -23.55, Longitude: -46.63, }) 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") } } }