mirror of
				https://github.com/3ybactuk/marketplace-go-service-project.git
				synced 2025-10-31 14:33:45 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			499 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
		
			11 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)
 | |
| }
 | |
| 
 | |
| type mockKafkaProducer struct{}
 | |
| 
 | |
| func (kp mockKafkaProducer) Send(_ context.Context, _ entity.ID, _ string) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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),
 | |
| 			},
 | |
| 			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),
 | |
| 			},
 | |
| 			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{}, &mockKafkaProducer{})
 | |
| 			_, 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{}, &mockKafkaProducer{})
 | |
| 			err := svc.OrderPay(ctx, tt.args.id)
 | |
| 			tt.wantErr(t, err)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLomsService_OrderInfoBadInput(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	svc := NewLomsService(
 | |
| 		nil,
 | |
| 		nil,
 | |
| 		&mockTxManager{},
 | |
| 		&mockKafkaProducer{},
 | |
| 	)
 | |
| 
 | |
| 	_, 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{},
 | |
| 		&mockKafkaProducer{},
 | |
| 	)
 | |
| 
 | |
| 	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{}, &mockKafkaProducer{})
 | |
| 			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{}, &mockKafkaProducer{})
 | |
| 			got, err := svc.StocksInfo(ctx, tt.args.sku)
 | |
| 			tt.wantErr(t, err)
 | |
| 			if err == nil {
 | |
| 				assert.Equal(t, tt.want, got)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | 
