Files
3ybactuk-marketplace-go-ser…/loms/internal/domain/service/service_test.go
2025-07-17 19:20:27 +00:00

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)
}
})
}
}