mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 05:53:45 +03:00
493 lines
10 KiB
Go
493 lines
10 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/loms/internal/domain/entity"
|
|
"route256/loms/internal/domain/model"
|
|
mock "route256/loms/internal/domain/service/mock"
|
|
|
|
pb "route256/pkg/api/loms/v1"
|
|
)
|
|
|
|
const (
|
|
testUser = entity.ID(1337)
|
|
testSku = entity.Sku(199)
|
|
)
|
|
|
|
type mockTxManager struct{}
|
|
|
|
func (t *mockTxManager) WriteWithTransaction(ctx context.Context, fn func(ctx context.Context) error) (err error) {
|
|
return fn(ctx)
|
|
}
|
|
|
|
func (t *mockTxManager) ReadWithTransaction(ctx context.Context, fn func(ctx context.Context) error) (err error) {
|
|
return fn(ctx)
|
|
}
|
|
|
|
func (t *mockTxManager) WriteWithRepeatableRead(ctx context.Context, fn func(ctx context.Context) error) (err error) {
|
|
return fn(ctx)
|
|
}
|
|
|
|
func (t *mockTxManager) ReadWithRepeatableRead(ctx context.Context, fn func(ctx context.Context) error) (err error) {
|
|
return fn(ctx)
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
goleak.VerifyTestMain(m)
|
|
}
|
|
|
|
func TestLomsService_OrderCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
mc := minimock.NewController(t)
|
|
|
|
goodReq := &pb.OrderCreateRequest{
|
|
UserId: int64(testUser),
|
|
Items: []*pb.OrderItem{
|
|
{
|
|
Sku: int64(testSku),
|
|
Count: 2,
|
|
},
|
|
},
|
|
}
|
|
|
|
badItemReq := &pb.OrderCreateRequest{
|
|
UserId: int64(testUser),
|
|
Items: []*pb.OrderItem{
|
|
{
|
|
Sku: 0,
|
|
Count: 0,
|
|
},
|
|
},
|
|
}
|
|
|
|
type fields struct {
|
|
orders OrderRepository
|
|
stocks StockRepository
|
|
}
|
|
type args struct {
|
|
req *pb.OrderCreateRequest
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "success",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderCreateMock.Return(1, nil).
|
|
OrderSetStatusMock.Return(nil),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockReserveMock.Return(nil),
|
|
},
|
|
args: args{
|
|
req: goodReq,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid input",
|
|
fields: fields{},
|
|
args: args{
|
|
req: &pb.OrderCreateRequest{
|
|
UserId: 0,
|
|
},
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
},
|
|
},
|
|
{
|
|
name: "invalid input with bad items",
|
|
fields: fields{},
|
|
args: args{
|
|
req: badItemReq,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
},
|
|
},
|
|
{
|
|
name: "order create error",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderCreateMock.Return(0, errors.New("order create error")),
|
|
stocks: nil,
|
|
},
|
|
args: args{
|
|
req: goodReq,
|
|
},
|
|
wantErr: require.Error,
|
|
},
|
|
{
|
|
name: "stock reserve error",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderCreateMock.Return(1, nil).
|
|
OrderSetStatusMock.Return(errors.New("status update error")),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockReserveMock.Return(errors.New("reservation error")),
|
|
},
|
|
args: args{
|
|
req: goodReq,
|
|
},
|
|
wantErr: require.Error,
|
|
},
|
|
{
|
|
name: "final status update error",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderCreateMock.Return(1, nil).
|
|
OrderSetStatusMock.Return(errors.New("unexpected error")),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockReserveMock.Return(nil),
|
|
},
|
|
args: args{
|
|
req: goodReq,
|
|
},
|
|
wantErr: require.Error,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc := NewLomsService(tt.fields.orders, tt.fields.stocks, &mockTxManager{})
|
|
_, err := svc.OrderCreate(ctx, tt.args.req)
|
|
tt.wantErr(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLomsService_OrderPay(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
mc := minimock.NewController(t)
|
|
|
|
awaitingOrder := &entity.Order{
|
|
OrderID: 1,
|
|
Status: pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String(),
|
|
Items: []entity.OrderItem{{
|
|
ID: testSku,
|
|
Count: 2,
|
|
}},
|
|
}
|
|
|
|
payedOrder := &entity.Order{OrderID: 2, Status: pb.OrderStatus_ORDER_STATUS_PAYED.String()}
|
|
badStatusOrder := &entity.Order{OrderID: 3, Status: pb.OrderStatus_ORDER_STATUS_FAILED.String()}
|
|
|
|
type fields struct {
|
|
orders OrderRepository
|
|
stocks StockRepository
|
|
}
|
|
type args struct {
|
|
id entity.ID
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "success",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(awaitingOrder, nil).
|
|
OrderSetStatusMock.Return(nil),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockReserveRemoveMock.Return(nil),
|
|
},
|
|
args: args{
|
|
id: awaitingOrder.OrderID,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid input",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc),
|
|
stocks: mock.NewStockRepositoryMock(mc),
|
|
},
|
|
args: args{
|
|
id: 0,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
},
|
|
},
|
|
{
|
|
name: "already payed",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(payedOrder, nil),
|
|
},
|
|
args: args{
|
|
id: payedOrder.OrderID,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid status",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(badStatusOrder, nil),
|
|
},
|
|
args: args{
|
|
id: badStatusOrder.OrderID,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrOrderInvalidStatus)
|
|
},
|
|
},
|
|
{
|
|
name: "unexpected logged error on updating stocks reserves",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(awaitingOrder, nil).
|
|
OrderSetStatusMock.Return(nil),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockReserveRemoveMock.Return(errors.New("unexpected error")),
|
|
},
|
|
args: args{
|
|
id: badStatusOrder.OrderID,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc := NewLomsService(tt.fields.orders, tt.fields.stocks, &mockTxManager{})
|
|
err := svc.OrderPay(ctx, tt.args.id)
|
|
tt.wantErr(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLomsService_OrderInfoBadInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc := NewLomsService(
|
|
nil,
|
|
nil,
|
|
&mockTxManager{},
|
|
)
|
|
|
|
_, err := svc.OrderInfo(context.Background(), 0)
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
}
|
|
|
|
func TestLomsService_OrderInfoSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
order := &entity.Order{
|
|
OrderID: 123,
|
|
Status: "payed",
|
|
UserID: 1337,
|
|
Items: []entity.OrderItem{},
|
|
}
|
|
|
|
mc := minimock.NewController(t)
|
|
svc := NewLomsService(
|
|
mock.NewOrderRepositoryMock(mc).OrderGetByIDMock.Return(order, nil),
|
|
nil,
|
|
&mockTxManager{},
|
|
)
|
|
|
|
gotOrder, err := svc.OrderInfo(context.Background(), 123)
|
|
require.NoError(t, err)
|
|
require.Equal(t, order, gotOrder)
|
|
}
|
|
|
|
func TestLomsService_OrderCancel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
mc := minimock.NewController(t)
|
|
|
|
awaiting := &entity.Order{
|
|
OrderID: 1,
|
|
Status: pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String(),
|
|
Items: []entity.OrderItem{{
|
|
ID: testSku, Count: 1,
|
|
}},
|
|
}
|
|
cancelled := &entity.Order{
|
|
OrderID: 2,
|
|
Status: pb.OrderStatus_ORDER_STATUS_CANCELLED.String(),
|
|
}
|
|
payed := &entity.Order{
|
|
OrderID: 3,
|
|
Status: pb.OrderStatus_ORDER_STATUS_PAYED.String(),
|
|
}
|
|
|
|
type fields struct {
|
|
orders OrderRepository
|
|
stocks StockRepository
|
|
}
|
|
type args struct {
|
|
id entity.ID
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "success",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(awaiting, nil).
|
|
OrderSetStatusMock.Return(nil),
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockCancelMock.Return(nil),
|
|
},
|
|
args: args{
|
|
id: awaiting.OrderID,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid input",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc),
|
|
stocks: mock.NewStockRepositoryMock(mc),
|
|
},
|
|
args: args{
|
|
id: 0,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
},
|
|
},
|
|
{
|
|
name: "already cancelled",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(cancelled, nil),
|
|
},
|
|
args: args{
|
|
id: cancelled.OrderID,
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid status",
|
|
fields: fields{
|
|
orders: mock.NewOrderRepositoryMock(mc).
|
|
OrderGetByIDMock.Return(payed, nil),
|
|
},
|
|
args: args{
|
|
id: payed.OrderID,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrOrderInvalidStatus)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc := NewLomsService(tt.fields.orders, tt.fields.stocks, &mockTxManager{})
|
|
err := svc.OrderCancel(ctx, tt.args.id)
|
|
tt.wantErr(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLomsService_StocksInfo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
mc := minimock.NewController(t)
|
|
|
|
type fields struct{ stocks StockRepository }
|
|
type args struct{ sku entity.Sku }
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want uint32
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "success",
|
|
fields: fields{
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockGetByIDMock.Return(&entity.Stock{
|
|
Item: entity.OrderItem{
|
|
Count: 7,
|
|
},
|
|
}, nil),
|
|
},
|
|
args: args{
|
|
sku: testSku,
|
|
},
|
|
want: 7,
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "invalid sku",
|
|
fields: fields{},
|
|
args: args{
|
|
sku: 0,
|
|
},
|
|
wantErr: func(t require.TestingT, err error, _ ...interface{}) {
|
|
require.ErrorIs(t, err, model.ErrInvalidInput)
|
|
},
|
|
},
|
|
{
|
|
name: "get by id error",
|
|
fields: fields{
|
|
stocks: mock.NewStockRepositoryMock(mc).
|
|
StockGetByIDMock.Return(nil, errors.New("unexpected error")),
|
|
},
|
|
args: args{
|
|
sku: testSku,
|
|
},
|
|
wantErr: require.Error,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc := NewLomsService(nil, tt.fields.stocks, &mockTxManager{})
|
|
got, err := svc.StocksInfo(ctx, tt.args.sku)
|
|
tt.wantErr(t, err)
|
|
if err == nil {
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|