mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 14:03:45 +03:00
[hw-3] loms service
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
type (
|
||||
UID uint64
|
||||
UID int64
|
||||
Sku int64
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import "errors"
|
||||
|
||||
var (
|
||||
ErrProductNotFound = errors.New("invalid sku")
|
||||
ErrNotEnoughStocks = errors.New("not enough stocks")
|
||||
|
||||
ErrCartNotFound = errors.New("cart not found")
|
||||
ErrItemNotFoundInCart = errors.New("item not found in cart")
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
)
|
||||
|
||||
type ProductService struct {
|
||||
httpClient http.Client
|
||||
token string
|
||||
address string
|
||||
}
|
||||
|
||||
func NewProductService(httpClient http.Client, token string, address string) *ProductService {
|
||||
return &ProductService{
|
||||
httpClient: httpClient,
|
||||
token: token,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProductService) GetProductBySku(ctx context.Context, sku entity.Sku) (*model.Product, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("http://%s/product/%d", s.address, sku),
|
||||
http.NoBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http.NewRequestWithContext: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("X-API-KEY", s.token)
|
||||
|
||||
response, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("httpClient.Do: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode == http.StatusNotFound {
|
||||
return nil, model.ErrProductNotFound
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("status not ok")
|
||||
}
|
||||
|
||||
resp := &GetProductResponse{}
|
||||
if err := json.NewDecoder(response.Body).Decode(resp); err != nil {
|
||||
return nil, fmt.Errorf("json.NewDecoder: %w", err)
|
||||
}
|
||||
|
||||
return &model.Product{
|
||||
Name: resp.Name,
|
||||
Price: resp.Price,
|
||||
Sku: entity.Sku(resp.Sku),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type GetProductResponse struct {
|
||||
Name string `json:"name"`
|
||||
Price int32 `json:"price"`
|
||||
Sku int64 `json:"sku"`
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:generate minimock -i Repository -o ./mock -s _mock.go
|
||||
@@ -24,15 +24,23 @@ type ProductService interface {
|
||||
GetProductBySku(ctx context.Context, sku entity.Sku) (*model.Product, error)
|
||||
}
|
||||
|
||||
type CartService struct {
|
||||
repository Repository
|
||||
productService ProductService
|
||||
type LomsService interface {
|
||||
OrderCreate(ctx context.Context, cart *model.Cart) (int64, error)
|
||||
StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error)
|
||||
}
|
||||
|
||||
func NewCartService(repository Repository, productService ProductService) *CartService {
|
||||
type CartService struct {
|
||||
repository Repository
|
||||
|
||||
productService ProductService
|
||||
lomsService LomsService
|
||||
}
|
||||
|
||||
func NewCartService(repository Repository, productService ProductService, lomsService LomsService) *CartService {
|
||||
return &CartService{
|
||||
repository: repository,
|
||||
productService: productService,
|
||||
lomsService: lomsService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +58,15 @@ func (s *CartService) AddItem(ctx context.Context, userID entity.UID, item *mode
|
||||
return fmt.Errorf("productService.GetProductBySku: %w", err)
|
||||
}
|
||||
|
||||
count, err := s.lomsService.StocksInfo(ctx, item.Product.Sku)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lomsService.StocksInfo: %w", err)
|
||||
}
|
||||
|
||||
if count < item.Count {
|
||||
return model.ErrNotEnoughStocks
|
||||
}
|
||||
|
||||
if err := s.repository.AddItem(ctx, userID, item); err != nil {
|
||||
return fmt.Errorf("repository.AddItemToCart: %w", err)
|
||||
}
|
||||
@@ -170,3 +187,25 @@ func (s *CartService) DeleteItemsByUserID(ctx context.Context, userID entity.UID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CartService) CheckoutUserCart(ctx context.Context, userID entity.UID) (int64, error) {
|
||||
if userID <= 0 {
|
||||
return 0, fmt.Errorf("userID invalid")
|
||||
}
|
||||
|
||||
cart, err := s.GetItemsByUserID(ctx, entity.UID(userID))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
orderID, err := s.lomsService.OrderCreate(ctx, cart)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("lomsService.OrderCreate: %w", err)
|
||||
}
|
||||
|
||||
if err := s.DeleteItemsByUserID(ctx, userID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return orderID, nil
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"route256/cart/internal/domain/cart/service/mock"
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
"route256/cart/internal/domain/service/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -19,9 +19,9 @@ const (
|
||||
validName = "some product name"
|
||||
)
|
||||
|
||||
type ProductServiceFake struct{}
|
||||
type productServiceFake struct{}
|
||||
|
||||
func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) {
|
||||
func (f *productServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) {
|
||||
if sku%2 == 0 {
|
||||
return nil, errors.New("empty shelf")
|
||||
}
|
||||
@@ -33,6 +33,24 @@ func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku)
|
||||
}, 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 TestCartService_AddItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -49,9 +67,20 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
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
|
||||
@@ -72,7 +101,8 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
AddItemMock.
|
||||
Expect(ctx, 1337, &testItem).
|
||||
Return(nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
lomsService: &lomsServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -118,7 +148,8 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
name: "product service error",
|
||||
fields: fields{
|
||||
repository: nil,
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
lomsService: &lomsServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -141,7 +172,8 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
AddItemMock.
|
||||
Expect(ctx, 1337, &testItem).
|
||||
Return(assert.AnError),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
lomsService: &lomsServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -150,6 +182,41 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
},
|
||||
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) {
|
||||
@@ -158,6 +225,7 @@ func TestCartService_AddItem(t *testing.T) {
|
||||
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)
|
||||
@@ -218,7 +286,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) {
|
||||
GetItemsByUserIDMock.
|
||||
Expect(ctx, testUID).
|
||||
Return(testEntityCart, nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -238,7 +306,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) {
|
||||
Items: []entity.Sku{testSKU},
|
||||
ItemCount: map[entity.Sku]uint32{testSKU: 2},
|
||||
}, nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -271,7 +339,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) {
|
||||
Items: []entity.Sku{testSKU, 1},
|
||||
ItemCount: map[entity.Sku]uint32{testSKU: 1, 1: 1},
|
||||
}, nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -357,7 +425,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) {
|
||||
Items: []entity.Sku{2},
|
||||
ItemCount: map[entity.Sku]uint32{2: 1},
|
||||
}, nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -377,7 +445,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) {
|
||||
Items: []entity.Sku{2},
|
||||
ItemCount: map[entity.Sku]uint32{2: 1},
|
||||
}, nil),
|
||||
productService: &ProductServiceFake{},
|
||||
productService: &productServiceFake{},
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
@@ -573,3 +641,118 @@ func TestCartService_DeleteItemsByUserID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user