Files
3ybactuk-marketplace-go-ser…/cart/internal/domain/service/service_test.go
2025-07-06 20:52:27 +00:00

779 lines
15 KiB
Go

package service
import (
"context"
"errors"
"testing"
"github.com/gojuno/minimock/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"route256/cart/internal/domain/entity"
"route256/cart/internal/domain/model"
"route256/cart/internal/domain/service/mock"
)
const (
validPrice = 123
validName = "some product name"
)
type productServiceFake struct{}
func (f *productServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) {
if sku%2 == 0 {
return nil, errors.New("empty shelf")
}
return &model.Product{
Name: validName,
Price: validPrice,
Sku: sku,
}, nil
}
func (f *productServiceFake) GetProducts(ctx context.Context, skus []entity.Sku) ([]*model.Product, error) {
res := make([]*model.Product, len(skus))
for i, sku := range skus {
prod, err := f.GetProductBySku(ctx, sku)
if err != nil {
return nil, err
}
res[i] = prod
}
return res, nil
}
type lomsServiceFake struct{}
func (f *lomsServiceFake) StocksInfo(_ context.Context, sku entity.Sku) (uint32, error) {
if sku == 1111 {
return 0, errors.New("stock error")
}
return 100, nil
}
func (f *lomsServiceFake) OrderCreate(_ context.Context, cart *model.Cart) (int64, error) {
if cart.UserID == 1111 {
return 0, errors.New("order create error")
}
return 1234, nil
}
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestCartService_AddItem(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testSKU := entity.Sku(199)
testItem := model.Item{
Product: &model.Product{
Name: validName,
Price: 123,
Sku: testSKU,
},
Count: 1,
}
testSKULomsFailing := entity.Sku(1111)
testItemLomsFailing := model.Item{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKULomsFailing,
},
Count: 1,
}
type fields struct {
repository Repository
productService ProductService
lomsService LomsService
}
type args struct {
ctx context.Context
item *model.Item
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
AddItemMock.
Expect(ctx, 1337, &testItem).
Return(nil),
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{
ctx: ctx,
item: &testItem,
userID: 1337,
},
wantErr: require.NoError,
},
{
name: "invalid item",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
item: &model.Item{
Product: &model.Product{
Name: "name",
Price: 123,
Sku: 0,
},
Count: 0,
},
userID: 0,
},
wantErr: require.Error,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
item: &testItem,
userID: 0,
},
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: nil,
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{
ctx: ctx,
item: &model.Item{
Product: &model.Product{
Name: "",
Price: 0,
Sku: 4,
},
Count: 1,
},
userID: 1337,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
AddItemMock.
Expect(ctx, 1337, &testItem).
Return(assert.AnError),
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{
ctx: ctx,
item: &testItem,
userID: 1337,
},
wantErr: require.Error,
},
{
name: "stocks acquiring error",
fields: fields{
repository: nil,
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{
ctx: ctx,
item: &testItemLomsFailing,
userID: 1337,
},
wantErr: require.Error,
},
{
name: "not enough stocks",
fields: fields{
repository: nil,
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{
ctx: ctx,
item: &model.Item{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 10000,
},
userID: 1337,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
productService: tt.fields.productService,
lomsService: tt.fields.lomsService,
}
err := s.AddItem(tt.args.ctx, tt.args.userID, tt.args.item)
tt.wantErr(t, err, "check add review error")
})
}
}
func TestCartService_GetItemsByUserID(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testUID := entity.UID(1337)
testSKU := entity.Sku(199)
testModelCart := model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 1,
},
},
TotalPrice: validPrice,
}
testEntityCart := entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU},
ItemCount: map[entity.Sku]uint32{testSKU: 1},
}
type fields struct {
repository Repository
productService ProductService
}
type args struct {
ctx context.Context
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
want *model.Cart
wantErr require.ErrorAssertionFunc
}{
{
name: "success 1 item",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(testEntityCart, nil),
productService: &productServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &testModelCart,
wantErr: require.NoError,
},
{
name: "success 2 items",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU},
ItemCount: map[entity.Sku]uint32{testSKU: 2},
}, nil),
productService: &productServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 2,
},
},
TotalPrice: validPrice * 2,
},
wantErr: require.NoError,
},
{
name: "success 2 different items",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU, 1},
ItemCount: map[entity.Sku]uint32{testSKU: 1, 1: 1},
}, nil),
productService: &productServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: 1,
},
Count: 1,
},
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 1,
},
},
TotalPrice: validPrice * 2,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
userID: 0,
},
want: nil,
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{}, assert.AnError),
productService: nil,
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "empty cart",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{}, nil),
productService: nil,
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{2},
ItemCount: map[entity.Sku]uint32{2: 1},
}, nil),
productService: &productServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{2},
ItemCount: map[entity.Sku]uint32{2: 1},
}, nil),
productService: &productServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
productService: tt.fields.productService,
}
got, err := s.GetItemsByUserID(tt.args.ctx, tt.args.userID)
tt.wantErr(t, err, "check add review error")
assert.True(t, assert.EqualExportedValues(t, tt.want, got), "got unexpected cart")
})
}
}
func TestCartService_DeleteItem(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testSKU := entity.Sku(199)
testUID := entity.UID(1337)
type fields struct {
repository Repository
}
type args struct {
ctx context.Context
userID entity.UID
sku entity.Sku
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemMock.
Expect(ctx, testUID, testSKU).
Return(nil),
},
args: args{
ctx: ctx,
userID: testUID,
sku: testSKU,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: 0,
sku: testSKU,
},
wantErr: require.Error,
},
{
name: "invalid sku",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: testUID,
sku: 0,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemMock.
Expect(ctx, testUID, testSKU).
Return(assert.AnError),
},
args: args{
ctx: ctx,
sku: testSKU,
userID: testUID,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
}
err := s.DeleteItem(tt.args.ctx, tt.args.userID, tt.args.sku)
tt.wantErr(t, err, "check add review error")
})
}
}
func TestCartService_DeleteItemsByUserID(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testUID := entity.UID(1337)
type fields struct {
repository Repository
}
type args struct {
ctx context.Context
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemsByUserIDMock.
Expect(ctx, testUID).
Return(nil),
},
args: args{
ctx: ctx,
userID: testUID,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: 0,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemsByUserIDMock.
Expect(ctx, testUID).
Return(assert.AnError),
},
args: args{
ctx: ctx,
userID: testUID,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
}
err := s.DeleteItemsByUserID(tt.args.ctx, tt.args.userID)
tt.wantErr(t, err, "check add review error")
})
}
}
func TestCartService_CheckoutUserCart(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testSKU := entity.Sku(199)
testCart := entity.Cart{
Items: []entity.Sku{testSKU},
ItemCount: map[entity.Sku]uint32{testSKU: 1},
}
type fields struct {
repository Repository
productService ProductService
lomsService LomsService
}
type args struct {
ctx context.Context
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
wantOrderID int64
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, 1337).
Return(testCart, nil).
DeleteItemsByUserIDMock.
Expect(ctx, 1337).
Return(nil),
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{ctx: ctx, userID: 1337},
wantOrderID: 1234,
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{},
args: args{ctx: ctx, userID: 0},
wantErr: require.Error,
},
{
name: "get cart error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, 1337).
Return(entity.Cart{}, assert.AnError),
},
args: args{ctx: ctx, userID: 1337},
wantErr: require.Error,
},
{
name: "order create error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, 1111).
Return(testCart, nil),
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{ctx: ctx, userID: 1111},
wantErr: require.Error,
},
{
name: "delete order error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, 1337).
Return(testCart, nil).
DeleteItemsByUserIDMock.
Expect(ctx, 1337).
Return(assert.AnError),
productService: &productServiceFake{},
lomsService: &lomsServiceFake{},
},
args: args{ctx: ctx, userID: 1337},
wantErr: require.Error,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
svc := &CartService{
repository: tt.fields.repository,
productService: tt.fields.productService,
lomsService: tt.fields.lomsService,
}
orderID, err := svc.CheckoutUserCart(tt.args.ctx, tt.args.userID)
tt.wantErr(t, err)
if err == nil {
require.Equal(t, tt.wantOrderID, orderID)
}
})
}
}