package pricing_test import ( "context" "errors" "testing" "github.com/google/uuid" "backend/internal/modules/pricing" ) // fakeRepository is an in-memory pricing.Repository used to unit-test // pricing.Service without a real database. type fakeRepository struct { tiers map[uuid.UUID]*pricing.PriceTier } func newFakeRepository() *fakeRepository { return &fakeRepository{tiers: map[uuid.UUID]*pricing.PriceTier{}} } func (r *fakeRepository) Create(_ context.Context, t *pricing.PriceTier) error { cp := *t r.tiers[t.ID] = &cp return nil } func (r *fakeRepository) FindByID(_ context.Context, id uuid.UUID) (*pricing.PriceTier, error) { t, ok := r.tiers[id] if !ok { return nil, pricing.ErrNotFound } cp := *t return &cp, nil } func (r *fakeRepository) ListByProduct(_ context.Context, productID uuid.UUID) ([]*pricing.PriceTier, error) { var list []*pricing.PriceTier for _, t := range r.tiers { if t.ProductID == productID { cp := *t list = append(list, &cp) } } return list, nil } func (r *fakeRepository) Update(_ context.Context, t *pricing.PriceTier) error { if _, ok := r.tiers[t.ID]; !ok { return pricing.ErrNotFound } cp := *t r.tiers[t.ID] = &cp return nil } func (r *fakeRepository) Delete(_ context.Context, id uuid.UUID) error { if _, ok := r.tiers[id]; !ok { return pricing.ErrNotFound } delete(r.tiers, id) return nil } func TestService_PriceForQuantity_MultipliesTierPrice(t *testing.T) { repo := newFakeRepository() svc := pricing.NewService(repo) ctx := context.Background() productID := uuid.New() unitID := uuid.New() // "5 kg -> 45.00" tier, spec section 16 example. tier, err := svc.Create(ctx, productID, unitID, 5, 4500, 0) if err != nil { t.Fatalf("Create() error = %v", err) } total, gotTier, err := svc.PriceForQuantity(ctx, tier.ID, 2) if err != nil { t.Fatalf("PriceForQuantity() error = %v", err) } if total != 9000 { t.Fatalf("PriceForQuantity() total = %d, want 9000 (2x 4500)", total) } if gotTier.ID != tier.ID { t.Fatalf("PriceForQuantity() returned tier %s, want %s", gotTier.ID, tier.ID) } } func TestService_PriceForQuantity_ZeroOrNegativeMultiplierTreatedAsOne(t *testing.T) { repo := newFakeRepository() svc := pricing.NewService(repo) ctx := context.Background() tier, err := svc.Create(ctx, uuid.New(), uuid.New(), 1, 1000, 0) if err != nil { t.Fatalf("Create() error = %v", err) } total, _, err := svc.PriceForQuantity(ctx, tier.ID, 0) if err != nil { t.Fatalf("PriceForQuantity() error = %v", err) } if total != 1000 { t.Fatalf("PriceForQuantity() with multiplier=0 total = %d, want 1000 (treated as 1x)", total) } } func TestService_PriceForQuantity_UnknownTierRejected(t *testing.T) { svc := pricing.NewService(newFakeRepository()) _, _, err := svc.PriceForQuantity(context.Background(), uuid.New(), 1) if !errors.Is(err, pricing.ErrNotFound) { t.Fatalf("PriceForQuantity() error = %v, want ErrNotFound", err) } } func TestService_ListByProduct_FiltersByProduct(t *testing.T) { repo := newFakeRepository() svc := pricing.NewService(repo) ctx := context.Background() productA := uuid.New() productB := uuid.New() unitID := uuid.New() if _, err := svc.Create(ctx, productA, unitID, 1, 1000, 0); err != nil { t.Fatalf("Create() error = %v", err) } if _, err := svc.Create(ctx, productB, unitID, 1, 2000, 0); err != nil { t.Fatalf("Create() error = %v", err) } list, err := svc.ListByProduct(ctx, productA) if err != nil { t.Fatalf("ListByProduct() error = %v", err) } if len(list) != 1 || list[0].ProductID != productA { t.Fatalf("ListByProduct(productA) = %+v, want exactly one tier for productA", list) } }