Compare commits
No commits in common. "master" and "diginavis" have entirely different histories.
24
README.md
24
README.md
|
@ -1,16 +1,14 @@
|
|||
# Тестовые задания
|
||||
Это единый репозиторий для всех тестовых заданий что я делал по просьбе
|
||||
компаний, кому было недостаточно просмотра кода в других моих репозиториях.
|
||||
# Тестовое задание
|
||||
Реализовать сервис корзины товаров:
|
||||
|
||||
## Нафига?
|
||||
Иногда мне предлагают тестовое, которое я уже решал. Иногда меня просят
|
||||
публиковать решения в различных площадках, затрудняя их поддержку. Этим
|
||||
репозиторием я хочу решить все эти проблемы, сэкономив как своё время, так и
|
||||
время рекрутеров.
|
||||
* Метод добавления/обновления товара в корзину с указанием количества товара
|
||||
* Метод удаление товара из корзины
|
||||
* Метод получения списка товаров, их количества и суммы
|
||||
|
||||
## Как?
|
||||
Каждое решение тестового задания пушится **в отдельную ветку** с именем
|
||||
компании со своей историей коммитов. После сдачи решения её код в репозитории
|
||||
не будет обновляться, дабы зафиксировать опыт.
|
||||
Протокол: gRPC
|
||||
Язык: Golang
|
||||
БД: postgres
|
||||
|
||||
Веселитесь!
|
||||
Приложение должно быть покрыто тестами, код разместить в виде публичного репозитория на github.
|
||||
|
||||
Для поднятия тестового окружения можно использовать docker & docker-compose, для CI - travis.
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//go:generate mockgen -package=model -source=./../../internal/model/model.pb.go -destination=./../../internal/model/model_mock.go CartShopClient
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"gitlab.com/toby3d/test/internal/client"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var flagAddr = flag.String("addr", ":2368", "set specific address and port for client instance")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
c, err := client.NewClient(*flagAddr)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
resp, err := c.Add(context.TODO(), &model.AddRequest{ProductId: 5, Quanity: 42})
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
if !resp.GetOk() {
|
||||
log.Printf("Get error on adding product: %s", resp.GetDescription())
|
||||
return
|
||||
}
|
||||
log.Printf(
|
||||
"Product %d has been added to cart, current quanity of this product is %d",
|
||||
resp.GetItem().GetProductId(), resp.GetItem().GetQuanity(),
|
||||
)
|
||||
|
||||
if resp, err = c.Get(context.TODO(), &model.GetRequest{}); err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
if !resp.GetOk() {
|
||||
log.Printf("Get error on getting cart: %s", resp.GetDescription())
|
||||
return
|
||||
}
|
||||
log.Printf(
|
||||
"Cart contains %d unique products (in %d quanity) with total price %g",
|
||||
resp.GetCart().GetItemsCount(),
|
||||
resp.GetCart().GetQuanityCount(),
|
||||
resp.GetCart().GetTotalPrice(),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
//go:generate protoc -I=./../../internal/model/ --go_out=plugins=grpc:./../../internal/model/ ./../../internal/model/model.proto
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"gitlab.com/toby3d/test/internal/db"
|
||||
"gitlab.com/toby3d/test/internal/handler"
|
||||
"gitlab.com/toby3d/test/internal/server"
|
||||
"gitlab.com/toby3d/test/internal/store"
|
||||
)
|
||||
|
||||
var (
|
||||
flagAddr = flag.String("addr", ":2368", "set specific address and port for server instance")
|
||||
flagDB = flag.String(
|
||||
"db", `host=/var/run/postgresql dbname=testing sslmode=disable`,
|
||||
"set specific parameters for connecting to database",
|
||||
)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
dataBase, err := db.Open(*flagDB)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
defer dataBase.Close()
|
||||
|
||||
if err = db.AutoMigrate(dataBase); err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
srv, err := server.NewServer(
|
||||
*flagAddr, handler.NewHandler(store.NewCartStore(dataBase), store.NewProductStore(dataBase)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
if err = srv.Start(); err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"golang.org/x/xerrors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Client представляет собой простой gRPC клиент
|
||||
type Client struct {
|
||||
model.ShopCartClient
|
||||
listener *grpc.ClientConn
|
||||
}
|
||||
|
||||
// ErrClientNotInitialized описывает ошибку инициализации сервера
|
||||
var ErrClientNotInitialized = xerrors.New("client is not initialized")
|
||||
|
||||
// NewClient создаёт новый клиент
|
||||
func NewClient(addr string) (*Client, error) {
|
||||
var c Client
|
||||
|
||||
var err error
|
||||
if c.listener, err = grpc.Dial(addr, grpc.WithInsecure()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.ShopCartClient = model.NewShopCartClient(c.listener)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Close закрывает все активные соединения с клиентом
|
||||
func (c *Client) Close() error {
|
||||
if c == nil || c.listener == nil {
|
||||
return ErrClientNotInitialized
|
||||
}
|
||||
return c.listener.Close()
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
/* TODO(toby3d): Почему-то невалидный адрес не вызывает никаких ошибок (упреждающее прослушивание?)
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
c, err := NewClient("wtf")
|
||||
assert.Error(t, err)
|
||||
t.Run("close", func(t *testing.T) {
|
||||
assert.Error(t, c.Close())
|
||||
})
|
||||
})
|
||||
*/
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
c, err := NewClient(":2368")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, c)
|
||||
t.Run("close", func(t *testing.T) {
|
||||
assert.NoError(t, c.Close())
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// demoProducts представляют собой демо-набор продуктов, добавляемые через AutoMigrate для дальнейшего чтения сторами
|
||||
var demoProducts = []*model.Product{
|
||||
&model.Product{Id: 24, Name: "Banana", Price: 2.49},
|
||||
&model.Product{Id: 42, Name: "Apple", Price: 4.99},
|
||||
&model.Product{Id: 420, Name: "Bottle of Soda", Price: 10},
|
||||
}
|
||||
|
||||
var ErrDataBaseNotInitialized = xerrors.New("database is not initialized")
|
||||
|
||||
// Open открывает соединение с PostgreSQL по указанному адресу с параметрами
|
||||
func Open(addr string) (*sqlx.DB, error) {
|
||||
client, err := sqlx.Connect("postgres", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = client.Ping(); err != nil {
|
||||
_ = client.Close()
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// AutoMigrate создаёт таблицы, если они не существуют, для адекватной работы сторов
|
||||
func AutoMigrate(db *sqlx.DB) (err error) {
|
||||
if db == nil || db.DB == nil {
|
||||
return ErrDataBaseNotInitialized
|
||||
}
|
||||
|
||||
if _, err = db.Exec("CREATE TABLE IF NOT EXISTS products (id SERIAL PRIMARY KEY, name TEXT, price FLOAT)"); err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range demoProducts {
|
||||
if _, err = db.Exec(
|
||||
"INSERT INTO products (id, name, price) VALUES ($1, $2, $3)",
|
||||
p.GetId(), p.GetName(), p.GetPrice(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if _, err = db.Exec("CREATE TABLE IF NOT EXISTS cart (product_id SERIAL PRIMARY KEY, quanity INT)"); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoClean удаляет таблицы созданные AutoMigrate
|
||||
func AutoClean(db *sqlx.DB) (err error) {
|
||||
if db == nil || db.DB == nil {
|
||||
return ErrDataBaseNotInitialized
|
||||
}
|
||||
|
||||
if _, err = db.Exec("DROP TABLE IF EXISTS cart"); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = db.Exec("DROP TABLE IF EXISTS products")
|
||||
return
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
db, err := Open(`wtf`)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, db)
|
||||
t.Run("auto migrate/clean", func(t *testing.T) {
|
||||
assert.Error(t, AutoMigrate(db))
|
||||
assert.Error(t, AutoClean(db))
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
db, err := Open(`host=/var/run/postgresql dbname=testing sslmode=disable`)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, db.Close()) }()
|
||||
t.Run("auto migrate/clean", func(t *testing.T) {
|
||||
assert.NoError(t, AutoMigrate(db))
|
||||
assert.NoError(t, AutoClean(db))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"gitlab.com/toby3d/test/internal/model/store"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Handler представляет собой объект хендлеров с хранилищем данных
|
||||
type Handler struct {
|
||||
cartManager store.CartManager
|
||||
productReader store.ProductReader
|
||||
}
|
||||
|
||||
// NewHandler создаёт хендлеры сервера с указанным хранилищем
|
||||
func NewHandler(cartManager store.CartManager, productReader store.ProductReader) *Handler {
|
||||
return &Handler{
|
||||
cartManager: cartManager,
|
||||
productReader: productReader,
|
||||
}
|
||||
}
|
||||
|
||||
// Add добавляет объект в хранилище (если его не существует) или обновляет количество существующего объекта
|
||||
func (h *Handler) Add(ctx context.Context, req *model.AddRequest) (*model.Response, error) {
|
||||
var resp model.Response
|
||||
|
||||
if err := h.cartManager.Add(&model.Item{
|
||||
ProductId: req.GetProductId(),
|
||||
Quanity: req.GetQuanity(),
|
||||
}); err != nil {
|
||||
resp.Description = err.Error()
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
resp.Ok = true
|
||||
resp.Result = &model.Response_Item{Item: h.cartManager.GetById(req.GetProductId())}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (h *Handler) Get(ctx context.Context, req *model.GetRequest) (*model.Response, error) {
|
||||
var resp model.Response
|
||||
|
||||
var result model.Cart
|
||||
count, items := h.cartManager.GetList()
|
||||
result.Items = items
|
||||
result.ItemsCount = int32(count)
|
||||
for _, item := range items {
|
||||
result.QuanityCount += item.GetQuanity()
|
||||
product := h.productReader.GetById(item.GetProductId())
|
||||
result.TotalPrice += product.GetPrice() * float32(item.GetQuanity())
|
||||
}
|
||||
// NOTE(toby3d): округляем до двух знаков после запятой
|
||||
result.TotalPrice = float32(math.Round(float64(result.GetTotalPrice())*100) / 100)
|
||||
|
||||
resp.Ok = true
|
||||
resp.Result = &model.Response_Cart{Cart: &result}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Update обновляет конкретные товары в корзине.
|
||||
func (h *Handler) Update(ctx context.Context, req *model.UpdateRequest) (*model.Response, error) {
|
||||
var resp model.Response
|
||||
|
||||
if err := h.cartManager.Update(&model.Item{
|
||||
ProductId: req.GetProductId(),
|
||||
Quanity: req.GetQuanity(),
|
||||
}); err != nil {
|
||||
resp.Description = err.Error()
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
// NOTE(toby3d): Если количество отрицательно, то Result должен быть пустой как и в случае Remove
|
||||
if req.GetQuanity() > 0 {
|
||||
resp.Result = &model.Response_Item{Item: h.cartManager.GetById(req.GetProductId())}
|
||||
}
|
||||
|
||||
resp.Ok = true
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Remove удаляет конкретные товары в корзине вне зависимости от их количества.
|
||||
func (h *Handler) Remove(ctx context.Context, req *model.RemoveRequest) (*model.Response, error) {
|
||||
var resp model.Response
|
||||
|
||||
if err := h.cartManager.Delete(req.GetProductId()); err != nil {
|
||||
resp.Description = err.Error()
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
resp.Ok = true
|
||||
return &resp, nil
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"gitlab.com/toby3d/test/internal/store"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var productsManager = store.InMemoryProductStore{Products: []*model.Product{
|
||||
&model.Product{Id: 24, Name: "Apple", Price: 4.99},
|
||||
&model.Product{Id: 42, Name: "Banana", Price: 2.49},
|
||||
}}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
handlerAdd := NewHandler(store.NewInMemoryCartStore(), &productsManager).Add
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
resp, err := handlerAdd(context.TODO(), &model.AddRequest{})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
})
|
||||
t.Run("zero quanity", func(t *testing.T) {
|
||||
resp, err := handlerAdd(context.TODO(), &model.AddRequest{ProductId: 42})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
handlerAdd := NewHandler(store.NewInMemoryCartStore(), &productsManager).Add
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
|
||||
resp, err := handlerAdd(context.TODO(), &model.AddRequest{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: itemOne.GetQuanity(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Equal(t, &itemOne, resp.GetItem())
|
||||
|
||||
t.Run("append to exist item", func(t *testing.T) {
|
||||
resp, err = handlerAdd(context.TODO(), &model.AddRequest{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: 24,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Equal(t, &model.Item{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: itemOne.GetQuanity() + 24,
|
||||
}, resp.GetItem())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
s := store.NewInMemoryCartStore()
|
||||
handlerGet := NewHandler(s, &productsManager).Get
|
||||
|
||||
resp, err := handlerGet(context.TODO(), &model.GetRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Empty(t, resp.GetCart())
|
||||
})
|
||||
t.Run("have items", func(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
items []*model.Item
|
||||
expCount int32
|
||||
expQuanity int32
|
||||
expPrice float32
|
||||
}{{
|
||||
name: "2 apples",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 24, Quanity: 2},
|
||||
},
|
||||
expCount: 1,
|
||||
expQuanity: 2,
|
||||
expPrice: 4.99 * 2,
|
||||
}, {
|
||||
name: "2 bananas",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 42, Quanity: 2},
|
||||
},
|
||||
expCount: 1,
|
||||
expQuanity: 2,
|
||||
expPrice: 2.49 * 2,
|
||||
}, {
|
||||
name: "2 bananas and apples",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 42, Quanity: 2},
|
||||
&model.Item{ProductId: 24, Quanity: 2},
|
||||
},
|
||||
expCount: 2,
|
||||
expQuanity: 4,
|
||||
expPrice: 2.49*2 + 4.99*2,
|
||||
}, {
|
||||
name: "5 bananas and 3 apples",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 42, Quanity: 5},
|
||||
&model.Item{ProductId: 24, Quanity: 3},
|
||||
},
|
||||
expCount: 2,
|
||||
expQuanity: 8,
|
||||
expPrice: 2.49*5 + 4.99*3,
|
||||
}, {
|
||||
name: "1+4 bananas and 3 apples",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 42, Quanity: 1},
|
||||
&model.Item{ProductId: 42, Quanity: 4},
|
||||
&model.Item{ProductId: 24, Quanity: 3},
|
||||
},
|
||||
expCount: 2,
|
||||
expQuanity: 8,
|
||||
expPrice: 2.49*5 + 4.99*3,
|
||||
}, {
|
||||
name: "2+3 bananas and 2+4 apples",
|
||||
items: []*model.Item{
|
||||
&model.Item{ProductId: 42, Quanity: 2},
|
||||
&model.Item{ProductId: 24, Quanity: 2},
|
||||
&model.Item{ProductId: 42, Quanity: 3},
|
||||
&model.Item{ProductId: 24, Quanity: 4},
|
||||
},
|
||||
expCount: 2,
|
||||
expQuanity: 11,
|
||||
expPrice: 2.49*5 + 4.99*6,
|
||||
}} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := store.NewInMemoryCartStore()
|
||||
for _, item := range tc.items {
|
||||
assert.NoError(t, s.Add(item))
|
||||
}
|
||||
handlerGet := NewHandler(s, &productsManager).Get
|
||||
|
||||
resp, err := handlerGet(context.TODO(), &model.GetRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
result := resp.GetCart()
|
||||
assert.Equal(t, tc.expCount, result.GetItemsCount())
|
||||
assert.Equal(t, tc.expQuanity, result.GetQuanityCount())
|
||||
assert.Equal(t, tc.expPrice, result.GetTotalPrice())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
handlerUpdate := NewHandler(store.NewInMemoryCartStore(), &productsManager).Update
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
resp, err := handlerUpdate(context.TODO(), &model.UpdateRequest{})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
})
|
||||
t.Run("not exists", func(t *testing.T) {
|
||||
resp, err := handlerUpdate(context.TODO(), &model.UpdateRequest{ProductId: 42})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("create", func(t *testing.T) {
|
||||
s := store.NewInMemoryCartStore()
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
handlerUpdate := NewHandler(s, &productsManager).Update
|
||||
|
||||
resp, err := handlerUpdate(
|
||||
context.TODO(),
|
||||
&model.UpdateRequest{ProductId: itemOne.GetProductId(), Quanity: itemOne.GetQuanity()},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Equal(t, &itemOne, resp.GetItem())
|
||||
})
|
||||
t.Run("update", func(t *testing.T) {
|
||||
s := store.NewInMemoryCartStore()
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
handlerUpdate := NewHandler(s, &productsManager).Update
|
||||
|
||||
resp, err := handlerUpdate(
|
||||
context.TODO(),
|
||||
&model.UpdateRequest{ProductId: itemOne.GetProductId(), Quanity: 2},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Equal(t, &model.Item{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: 2,
|
||||
}, resp.GetItem())
|
||||
})
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
s := store.NewInMemoryCartStore()
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
handlerUpdate := NewHandler(s, &productsManager).Update
|
||||
|
||||
resp, err := handlerUpdate(
|
||||
context.TODO(),
|
||||
&model.UpdateRequest{ProductId: itemOne.GetProductId(), Quanity: 0},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
|
||||
assert.Empty(t, resp.GetResult())
|
||||
})
|
||||
})
|
||||
}
|
||||
func TestRemove(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
|
||||
s := store.NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
handlerRemove := NewHandler(s, &productsManager).Remove
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
resp, err := handlerRemove(context.TODO(), &model.RemoveRequest{})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("not exist", func(t *testing.T) {
|
||||
resp, err := handlerRemove(context.TODO(), &model.RemoveRequest{ProductId: 24})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, resp.GetOk())
|
||||
assert.NotEmpty(t, resp.GetDescription())
|
||||
assert.Nil(t, resp.GetResult())
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 5}
|
||||
|
||||
s := store.NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
handlerRemove := NewHandler(s, &productsManager).Remove
|
||||
|
||||
resp, err := handlerRemove(
|
||||
context.TODO(), &model.RemoveRequest{ProductId: itemOne.GetProductId()},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resp.GetOk())
|
||||
assert.Empty(t, resp.GetDescription())
|
||||
assert.Empty(t, resp.GetResult())
|
||||
count, list := s.GetList()
|
||||
assert.NotContains(t, list, &itemOne)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,681 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: model.proto
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Product struct {
|
||||
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Price float32 `protobuf:"fixed32,3,opt,name=price,proto3" json:"price,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Product) Reset() { *m = Product{} }
|
||||
func (m *Product) String() string { return proto.CompactTextString(m) }
|
||||
func (*Product) ProtoMessage() {}
|
||||
func (*Product) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{0}
|
||||
}
|
||||
|
||||
func (m *Product) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Product.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Product) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Product.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Product) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Product.Merge(m, src)
|
||||
}
|
||||
func (m *Product) XXX_Size() int {
|
||||
return xxx_messageInfo_Product.Size(m)
|
||||
}
|
||||
func (m *Product) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Product.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Product proto.InternalMessageInfo
|
||||
|
||||
func (m *Product) GetId() uint64 {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Product) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Product) GetPrice() float32 {
|
||||
if m != nil {
|
||||
return m.Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
ProductId uint64 `protobuf:"varint,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
|
||||
Quanity int32 `protobuf:"varint,2,opt,name=quanity,proto3" json:"quanity,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Item) Reset() { *m = Item{} }
|
||||
func (m *Item) String() string { return proto.CompactTextString(m) }
|
||||
func (*Item) ProtoMessage() {}
|
||||
func (*Item) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{1}
|
||||
}
|
||||
|
||||
func (m *Item) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Item.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Item) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Item.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Item) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Item.Merge(m, src)
|
||||
}
|
||||
func (m *Item) XXX_Size() int {
|
||||
return xxx_messageInfo_Item.Size(m)
|
||||
}
|
||||
func (m *Item) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Item.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Item proto.InternalMessageInfo
|
||||
|
||||
func (m *Item) GetProductId() uint64 {
|
||||
if m != nil {
|
||||
return m.ProductId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Item) GetQuanity() int32 {
|
||||
if m != nil {
|
||||
return m.Quanity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Cart struct {
|
||||
Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
TotalPrice float32 `protobuf:"fixed32,2,opt,name=total_price,json=totalPrice,proto3" json:"total_price,omitempty"`
|
||||
ItemsCount int32 `protobuf:"varint,3,opt,name=items_count,json=itemsCount,proto3" json:"items_count,omitempty"`
|
||||
QuanityCount int32 `protobuf:"varint,4,opt,name=quanity_count,json=quanityCount,proto3" json:"quanity_count,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Cart) Reset() { *m = Cart{} }
|
||||
func (m *Cart) String() string { return proto.CompactTextString(m) }
|
||||
func (*Cart) ProtoMessage() {}
|
||||
func (*Cart) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{2}
|
||||
}
|
||||
|
||||
func (m *Cart) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Cart.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Cart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Cart.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Cart) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Cart.Merge(m, src)
|
||||
}
|
||||
func (m *Cart) XXX_Size() int {
|
||||
return xxx_messageInfo_Cart.Size(m)
|
||||
}
|
||||
func (m *Cart) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Cart.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Cart proto.InternalMessageInfo
|
||||
|
||||
func (m *Cart) GetItems() []*Item {
|
||||
if m != nil {
|
||||
return m.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Cart) GetTotalPrice() float32 {
|
||||
if m != nil {
|
||||
return m.TotalPrice
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cart) GetItemsCount() int32 {
|
||||
if m != nil {
|
||||
return m.ItemsCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cart) GetQuanityCount() int32 {
|
||||
if m != nil {
|
||||
return m.QuanityCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AddRequest struct {
|
||||
ProductId uint64 `protobuf:"varint,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
|
||||
Quanity int32 `protobuf:"varint,2,opt,name=quanity,proto3" json:"quanity,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AddRequest) Reset() { *m = AddRequest{} }
|
||||
func (m *AddRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AddRequest) ProtoMessage() {}
|
||||
func (*AddRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{3}
|
||||
}
|
||||
|
||||
func (m *AddRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AddRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AddRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AddRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *AddRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AddRequest.Merge(m, src)
|
||||
}
|
||||
func (m *AddRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_AddRequest.Size(m)
|
||||
}
|
||||
func (m *AddRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AddRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AddRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *AddRequest) GetProductId() uint64 {
|
||||
if m != nil {
|
||||
return m.ProductId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *AddRequest) GetQuanity() int32 {
|
||||
if m != nil {
|
||||
return m.Quanity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GetRequest struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetRequest) Reset() { *m = GetRequest{} }
|
||||
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetRequest) ProtoMessage() {}
|
||||
func (*GetRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{4}
|
||||
}
|
||||
|
||||
func (m *GetRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetRequest.Merge(m, src)
|
||||
}
|
||||
func (m *GetRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_GetRequest.Size(m)
|
||||
}
|
||||
func (m *GetRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetRequest proto.InternalMessageInfo
|
||||
|
||||
type UpdateRequest struct {
|
||||
ProductId uint64 `protobuf:"varint,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
|
||||
Quanity int32 `protobuf:"varint,2,opt,name=quanity,proto3" json:"quanity,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) Reset() { *m = UpdateRequest{} }
|
||||
func (m *UpdateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*UpdateRequest) ProtoMessage() {}
|
||||
func (*UpdateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{5}
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_UpdateRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_UpdateRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_UpdateRequest.Merge(m, src)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_UpdateRequest.Size(m)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_UpdateRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_UpdateRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *UpdateRequest) GetProductId() uint64 {
|
||||
if m != nil {
|
||||
return m.ProductId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) GetQuanity() int32 {
|
||||
if m != nil {
|
||||
return m.Quanity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type RemoveRequest struct {
|
||||
ProductId uint64 `protobuf:"varint,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *RemoveRequest) Reset() { *m = RemoveRequest{} }
|
||||
func (m *RemoveRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*RemoveRequest) ProtoMessage() {}
|
||||
func (*RemoveRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{6}
|
||||
}
|
||||
|
||||
func (m *RemoveRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_RemoveRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *RemoveRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_RemoveRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *RemoveRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RemoveRequest.Merge(m, src)
|
||||
}
|
||||
func (m *RemoveRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_RemoveRequest.Size(m)
|
||||
}
|
||||
func (m *RemoveRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RemoveRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RemoveRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *RemoveRequest) GetProductId() uint64 {
|
||||
if m != nil {
|
||||
return m.ProductId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
|
||||
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
|
||||
// Types that are valid to be assigned to Result:
|
||||
// *Response_Item
|
||||
// *Response_Cart
|
||||
Result isResponse_Result `protobuf_oneof:"result"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_4c16552f9fdb66d8, []int{7}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetOk() bool {
|
||||
if m != nil {
|
||||
return m.Ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Response) GetDescription() string {
|
||||
if m != nil {
|
||||
return m.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type isResponse_Result interface {
|
||||
isResponse_Result()
|
||||
}
|
||||
|
||||
type Response_Item struct {
|
||||
Item *Item `protobuf:"bytes,3,opt,name=item,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Response_Cart struct {
|
||||
Cart *Cart `protobuf:"bytes,4,opt,name=cart,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Response_Item) isResponse_Result() {}
|
||||
|
||||
func (*Response_Cart) isResponse_Result() {}
|
||||
|
||||
func (m *Response) GetResult() isResponse_Result {
|
||||
if m != nil {
|
||||
return m.Result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetItem() *Item {
|
||||
if x, ok := m.GetResult().(*Response_Item); ok {
|
||||
return x.Item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetCart() *Cart {
|
||||
if x, ok := m.GetResult().(*Response_Cart); ok {
|
||||
return x.Cart
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||
func (*Response) XXX_OneofWrappers() []interface{} {
|
||||
return []interface{}{
|
||||
(*Response_Item)(nil),
|
||||
(*Response_Cart)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Product)(nil), "model.Product")
|
||||
proto.RegisterType((*Item)(nil), "model.Item")
|
||||
proto.RegisterType((*Cart)(nil), "model.Cart")
|
||||
proto.RegisterType((*AddRequest)(nil), "model.AddRequest")
|
||||
proto.RegisterType((*GetRequest)(nil), "model.GetRequest")
|
||||
proto.RegisterType((*UpdateRequest)(nil), "model.UpdateRequest")
|
||||
proto.RegisterType((*RemoveRequest)(nil), "model.RemoveRequest")
|
||||
proto.RegisterType((*Response)(nil), "model.Response")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("model.proto", fileDescriptor_4c16552f9fdb66d8) }
|
||||
|
||||
var fileDescriptor_4c16552f9fdb66d8 = []byte{
|
||||
// 402 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x4a, 0xc3, 0x40,
|
||||
0x10, 0xc6, 0xbb, 0x69, 0xd2, 0xa6, 0x93, 0x56, 0x71, 0xe9, 0x21, 0x08, 0x62, 0x1a, 0x2f, 0x01,
|
||||
0xa1, 0x60, 0x7d, 0x00, 0xa9, 0x45, 0xda, 0xde, 0xca, 0x8a, 0xe7, 0x12, 0xb3, 0x0b, 0x86, 0x36,
|
||||
0xd9, 0x34, 0xd9, 0x08, 0xbe, 0x83, 0x07, 0x9f, 0xcb, 0xa7, 0x92, 0xfd, 0xd3, 0xd6, 0x42, 0x05,
|
||||
0xd1, 0x5b, 0xe6, 0x9b, 0xdf, 0x7c, 0x99, 0x7c, 0x43, 0xc0, 0xcb, 0x38, 0x65, 0xeb, 0x61, 0x51,
|
||||
0x72, 0xc1, 0xb1, 0xa3, 0x8a, 0x70, 0x02, 0xed, 0x45, 0xc9, 0x69, 0x9d, 0x08, 0x7c, 0x02, 0x56,
|
||||
0x4a, 0x7d, 0x14, 0xa0, 0xc8, 0x26, 0x56, 0x4a, 0x31, 0x06, 0x3b, 0x8f, 0x33, 0xe6, 0x5b, 0x01,
|
||||
0x8a, 0x3a, 0x44, 0x3d, 0xe3, 0x3e, 0x38, 0x45, 0x99, 0x26, 0xcc, 0x6f, 0x06, 0x28, 0xb2, 0x88,
|
||||
0x2e, 0xc2, 0x3b, 0xb0, 0xe7, 0x82, 0x65, 0xf8, 0x02, 0xa0, 0xd0, 0x66, 0xcb, 0x9d, 0x53, 0xc7,
|
||||
0x28, 0x73, 0x8a, 0x7d, 0x68, 0x6f, 0xea, 0x38, 0x4f, 0xc5, 0x9b, 0xf2, 0x74, 0xc8, 0xb6, 0x0c,
|
||||
0x3f, 0x10, 0xd8, 0x93, 0xb8, 0x14, 0x78, 0x00, 0x4e, 0x2a, 0x58, 0x56, 0xf9, 0x28, 0x68, 0x46,
|
||||
0xde, 0xc8, 0x1b, 0xea, 0x95, 0xa5, 0x3b, 0xd1, 0x1d, 0x7c, 0x09, 0x9e, 0xe0, 0x22, 0x5e, 0x2f,
|
||||
0xf5, 0x22, 0x96, 0x5a, 0x04, 0x94, 0xb4, 0x90, 0x8a, 0x04, 0x14, 0xb9, 0x4c, 0x78, 0x9d, 0x0b,
|
||||
0xb5, 0xa9, 0x43, 0x40, 0x49, 0x13, 0xa9, 0xe0, 0x2b, 0xe8, 0x99, 0x17, 0x1b, 0xc4, 0x56, 0x48,
|
||||
0xd7, 0x88, 0x0a, 0x0a, 0x1f, 0x00, 0xc6, 0x94, 0x12, 0xb6, 0xa9, 0x59, 0x25, 0xfe, 0xfe, 0x65,
|
||||
0x5d, 0x80, 0x29, 0x13, 0xc6, 0x26, 0x9c, 0x41, 0xef, 0xa9, 0xa0, 0xb1, 0x60, 0xff, 0xf6, 0x1d,
|
||||
0x42, 0x8f, 0xb0, 0x8c, 0xbf, 0xfe, 0xd2, 0x29, 0x7c, 0x47, 0xe0, 0x12, 0x56, 0x15, 0x3c, 0xaf,
|
||||
0x98, 0xbc, 0x34, 0x5f, 0x29, 0xc6, 0x25, 0x16, 0x5f, 0xe1, 0x00, 0x3c, 0xca, 0xaa, 0xa4, 0x4c,
|
||||
0x0b, 0x91, 0xf2, 0xdc, 0x1c, 0xfc, 0xbb, 0x84, 0x07, 0x60, 0xcb, 0x00, 0x55, 0x98, 0x87, 0x67,
|
||||
0x99, 0x35, 0x88, 0x6a, 0x49, 0x24, 0x89, 0x4b, 0x1d, 0xe6, 0x1e, 0x91, 0x57, 0x95, 0x88, 0x6c,
|
||||
0xdd, 0xbb, 0xd0, 0x2a, 0x59, 0x55, 0xaf, 0xc5, 0xe8, 0x13, 0x81, 0xfb, 0xf8, 0xc2, 0x0b, 0x75,
|
||||
0xf4, 0x6b, 0x68, 0x8e, 0x29, 0xc5, 0x67, 0x66, 0x64, 0x1f, 0xfb, 0xf9, 0xa9, 0x91, 0xb6, 0x9b,
|
||||
0x87, 0x0d, 0x09, 0x4f, 0x99, 0xd8, 0xc1, 0xfb, 0x70, 0x8f, 0xc1, 0x37, 0xd0, 0xd2, 0x79, 0xe3,
|
||||
0xbe, 0x69, 0x1e, 0xc4, 0xff, 0xc3, 0x88, 0x0e, 0x76, 0x37, 0x72, 0x90, 0xf3, 0x91, 0x91, 0xe7,
|
||||
0x96, 0xfa, 0xa3, 0x6e, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb4, 0x9b, 0xe9, 0x0b, 0x60, 0x03,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// ShopCartClient is the client API for ShopCart service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ShopCartClient interface {
|
||||
Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*Response, error)
|
||||
Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Response, error)
|
||||
Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Response, error)
|
||||
Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
type shopCartClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewShopCartClient(cc *grpc.ClientConn) ShopCartClient {
|
||||
return &shopCartClient{cc}
|
||||
}
|
||||
|
||||
func (c *shopCartClient) Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/model.ShopCart/Add", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *shopCartClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/model.ShopCart/Get", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *shopCartClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/model.ShopCart/Update", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *shopCartClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/model.ShopCart/Remove", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ShopCartServer is the server API for ShopCart service.
|
||||
type ShopCartServer interface {
|
||||
Add(context.Context, *AddRequest) (*Response, error)
|
||||
Get(context.Context, *GetRequest) (*Response, error)
|
||||
Update(context.Context, *UpdateRequest) (*Response, error)
|
||||
Remove(context.Context, *RemoveRequest) (*Response, error)
|
||||
}
|
||||
|
||||
// UnimplementedShopCartServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedShopCartServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedShopCartServer) Add(ctx context.Context, req *AddRequest) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Add not implemented")
|
||||
}
|
||||
func (*UnimplementedShopCartServer) Get(ctx context.Context, req *GetRequest) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
|
||||
}
|
||||
func (*UnimplementedShopCartServer) Update(ctx context.Context, req *UpdateRequest) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Update not implemented")
|
||||
}
|
||||
func (*UnimplementedShopCartServer) Remove(ctx context.Context, req *RemoveRequest) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Remove not implemented")
|
||||
}
|
||||
|
||||
func RegisterShopCartServer(s *grpc.Server, srv ShopCartServer) {
|
||||
s.RegisterService(&_ShopCart_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _ShopCart_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ShopCartServer).Add(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/model.ShopCart/Add",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ShopCartServer).Add(ctx, req.(*AddRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ShopCart_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ShopCartServer).Get(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/model.ShopCart/Get",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ShopCartServer).Get(ctx, req.(*GetRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ShopCart_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ShopCartServer).Update(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/model.ShopCart/Update",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ShopCartServer).Update(ctx, req.(*UpdateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ShopCart_Remove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RemoveRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ShopCartServer).Remove(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/model.ShopCart/Remove",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ShopCartServer).Remove(ctx, req.(*RemoveRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _ShopCart_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "model.ShopCart",
|
||||
HandlerType: (*ShopCartServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Add",
|
||||
Handler: _ShopCart_Add_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Get",
|
||||
Handler: _ShopCart_Get_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Update",
|
||||
Handler: _ShopCart_Update_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Remove",
|
||||
Handler: _ShopCart_Remove_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "model.proto",
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
syntax = "proto3";
|
||||
package model;
|
||||
|
||||
service ShopCart { // Сервис корзины товаров
|
||||
rpc Add (AddRequest) returns (Response) {} // Добавление товара в корзину
|
||||
rpc Get (GetRequest) returns (Response) {} // Get получение текущего состояния корзины
|
||||
rpc Update (UpdateRequest) returns (Response) {} // Обновление товара в корзине
|
||||
rpc Remove (RemoveRequest) returns (Response) {} // Удаление товара из корзины
|
||||
}
|
||||
|
||||
message Product { // Модель продукта доступного для добавления в корзину
|
||||
uint64 id = 1; // ID продукта
|
||||
string name = 2; // Имя/название продукта
|
||||
float price = 3; // Цена за единицу продукта
|
||||
}
|
||||
|
||||
message Item { // Модель объекта корзины
|
||||
uint64 product_id = 1; // ID продукта
|
||||
int32 quanity = 2; // Количество объекта в корзине
|
||||
}
|
||||
|
||||
message Cart {
|
||||
repeated Item items = 1; // Список всех товаров в корзине
|
||||
float total_price = 2; // Общая сумма за все товары
|
||||
int32 items_count = 3; // Число уникальных товаров в корзине
|
||||
int32 quanity_count = 4; // Общая сумма количества всех товаров
|
||||
}
|
||||
|
||||
message AddRequest {
|
||||
uint64 product_id = 1; // ID продукта
|
||||
int32 quanity = 2; // Добавляемое количество продуктов
|
||||
}
|
||||
|
||||
message GetRequest {} // Без каких-либо специальных параметров
|
||||
|
||||
message UpdateRequest {
|
||||
uint64 product_id = 1; // ID продукта
|
||||
int32 quanity = 2; // Новое количество продуктов
|
||||
}
|
||||
|
||||
message RemoveRequest {
|
||||
uint64 product_id = 1; // ID продукта
|
||||
}
|
||||
|
||||
message Response { // Модель ответа
|
||||
bool ok = 1; // Тип ответа: успешный или нет
|
||||
string description = 2; // Описание ошибки в случае невалидного ответа
|
||||
oneof result { // Результаты запросов
|
||||
Item item = 3;
|
||||
Cart cart = 4;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./../../internal/model/model.pb.go
|
||||
|
||||
// Package model is a generated GoMock package.
|
||||
package model
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
grpc "google.golang.org/grpc"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockisResponse_Result is a mock of isResponse_Result interface
|
||||
type MockisResponse_Result struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockisResponse_ResultMockRecorder
|
||||
}
|
||||
|
||||
// MockisResponse_ResultMockRecorder is the mock recorder for MockisResponse_Result
|
||||
type MockisResponse_ResultMockRecorder struct {
|
||||
mock *MockisResponse_Result
|
||||
}
|
||||
|
||||
// NewMockisResponse_Result creates a new mock instance
|
||||
func NewMockisResponse_Result(ctrl *gomock.Controller) *MockisResponse_Result {
|
||||
mock := &MockisResponse_Result{ctrl: ctrl}
|
||||
mock.recorder = &MockisResponse_ResultMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockisResponse_Result) EXPECT() *MockisResponse_ResultMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// isResponse_Result mocks base method
|
||||
func (m *MockisResponse_Result) isResponse_Result() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "isResponse_Result")
|
||||
}
|
||||
|
||||
// isResponse_Result indicates an expected call of isResponse_Result
|
||||
func (mr *MockisResponse_ResultMockRecorder) isResponse_Result() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isResponse_Result", reflect.TypeOf((*MockisResponse_Result)(nil).isResponse_Result))
|
||||
}
|
||||
|
||||
// MockShopCartClient is a mock of ShopCartClient interface
|
||||
type MockShopCartClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockShopCartClientMockRecorder
|
||||
}
|
||||
|
||||
// MockShopCartClientMockRecorder is the mock recorder for MockShopCartClient
|
||||
type MockShopCartClientMockRecorder struct {
|
||||
mock *MockShopCartClient
|
||||
}
|
||||
|
||||
// NewMockShopCartClient creates a new mock instance
|
||||
func NewMockShopCartClient(ctrl *gomock.Controller) *MockShopCartClient {
|
||||
mock := &MockShopCartClient{ctrl: ctrl}
|
||||
mock.recorder = &MockShopCartClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockShopCartClient) EXPECT() *MockShopCartClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockShopCartClient) Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Add", varargs...)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockShopCartClientMockRecorder) Add(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockShopCartClient)(nil).Add), varargs...)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockShopCartClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Get", varargs...)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockShopCartClientMockRecorder) Get(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockShopCartClient)(nil).Get), varargs...)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockShopCartClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Update", varargs...)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockShopCartClientMockRecorder) Update(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockShopCartClient)(nil).Update), varargs...)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockShopCartClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Remove", varargs...)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockShopCartClientMockRecorder) Remove(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockShopCartClient)(nil).Remove), varargs...)
|
||||
}
|
||||
|
||||
// MockShopCartServer is a mock of ShopCartServer interface
|
||||
type MockShopCartServer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockShopCartServerMockRecorder
|
||||
}
|
||||
|
||||
// MockShopCartServerMockRecorder is the mock recorder for MockShopCartServer
|
||||
type MockShopCartServerMockRecorder struct {
|
||||
mock *MockShopCartServer
|
||||
}
|
||||
|
||||
// NewMockShopCartServer creates a new mock instance
|
||||
func NewMockShopCartServer(ctrl *gomock.Controller) *MockShopCartServer {
|
||||
mock := &MockShopCartServer{ctrl: ctrl}
|
||||
mock.recorder = &MockShopCartServerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockShopCartServer) EXPECT() *MockShopCartServerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockShopCartServer) Add(arg0 context.Context, arg1 *AddRequest) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Add", arg0, arg1)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockShopCartServerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockShopCartServer)(nil).Add), arg0, arg1)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockShopCartServer) Get(arg0 context.Context, arg1 *GetRequest) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0, arg1)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockShopCartServerMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockShopCartServer)(nil).Get), arg0, arg1)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockShopCartServer) Update(arg0 context.Context, arg1 *UpdateRequest) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", arg0, arg1)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockShopCartServerMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockShopCartServer)(nil).Update), arg0, arg1)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockShopCartServer) Remove(arg0 context.Context, arg1 *RemoveRequest) (*Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", arg0, arg1)
|
||||
ret0, _ := ret[0].(*Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockShopCartServerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockShopCartServer)(nil).Remove), arg0, arg1)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package store
|
||||
|
||||
import "gitlab.com/toby3d/test/internal/model"
|
||||
|
||||
type (
|
||||
CartManager interface {
|
||||
Add(item *model.Item) error
|
||||
Delete(id uint64) error
|
||||
GetById(id uint64) *model.Item
|
||||
GetList() (int, []*model.Item)
|
||||
Update(item *model.Item) error
|
||||
}
|
||||
|
||||
ProductReader interface {
|
||||
GetById(id uint64) *model.Product
|
||||
}
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"golang.org/x/xerrors"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
// Server представляет собой объект gRPC сервера
|
||||
type Server struct {
|
||||
listener net.Listener
|
||||
server *grpc.Server
|
||||
}
|
||||
|
||||
// ErrServerNotInitialized описывает ошибку инициализации сервера
|
||||
var ErrServerNotInitialized = xerrors.New("server is not initialized")
|
||||
|
||||
// NewServer создаёт новое TCP соединение по указанному адресу с указанным набором хендлеров
|
||||
func NewServer(addr string, handlers model.ShopCartServer) (*Server, error) {
|
||||
s := Server{server: grpc.NewServer()}
|
||||
|
||||
var err error
|
||||
if s.listener, err = net.Listen("tcp", addr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model.RegisterShopCartServer(s.server, handlers)
|
||||
reflection.Register(s.server)
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Start запускает gRPC сервер.
|
||||
// Возвращает ошибку если была предпринята попытка запуска без предварительной инициализации сервера.
|
||||
func (s *Server) Start() error {
|
||||
if s == nil || s.server == nil || s.listener == nil {
|
||||
return ErrServerNotInitialized
|
||||
}
|
||||
return s.server.Serve(s.listener)
|
||||
}
|
||||
|
||||
// Stop останавливает gRPC сервер.
|
||||
// Возвращает ошибку если была предпринята попытка остановки без предварительной инициализации сервера.
|
||||
func (s *Server) Stop() error {
|
||||
if s == nil || s.server == nil {
|
||||
return ErrServerNotInitialized
|
||||
}
|
||||
|
||||
s.server.Stop()
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/toby3d/test/internal/handler"
|
||||
"gitlab.com/toby3d/test/internal/store"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
srv, err := NewServer("wtf", nil)
|
||||
assert.Error(t, err)
|
||||
t.Run("start/stop", func(t *testing.T) {
|
||||
assert.Error(t, srv.Start())
|
||||
assert.Error(t, srv.Stop())
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
srv, err := NewServer(":2368", handler.NewHandler(
|
||||
store.NewInMemoryCartStore(), store.NewInMemoryProductStore(),
|
||||
))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, srv)
|
||||
t.Run("start/stop", func(t *testing.T) {
|
||||
go func() { assert.NoError(t, srv.Start()) }()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
assert.NoError(t, srv.Stop())
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type (
|
||||
// InMemoryProductStore представляет собой объект хранилища продуктов доступный только для чтения
|
||||
InMemoryProductStore struct {
|
||||
mutex sync.RWMutex
|
||||
Products []*model.Product
|
||||
}
|
||||
|
||||
// InMemoryCartStore представляет собой простой менеджер объектов корзины.
|
||||
InMemoryCartStore struct {
|
||||
mutex sync.RWMutex
|
||||
items []*model.Item
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoProductId = xerrors.New("product_id not provided")
|
||||
ErrZeroQuanity = xerrors.New("item quanity is zero")
|
||||
ErrNotExist = xerrors.New("item not exists or already removed")
|
||||
)
|
||||
|
||||
// NewInMemoryProductStore создаёт новый менеджер продуктов
|
||||
func NewInMemoryProductStore() *InMemoryProductStore {
|
||||
return &InMemoryProductStore{mutex: sync.RWMutex{}}
|
||||
}
|
||||
|
||||
// GetById возвращает информацию о продукте по его ID, если он существует
|
||||
func (imps *InMemoryProductStore) GetById(id uint64) *model.Product {
|
||||
for _, product := range imps.Products {
|
||||
if product.GetId() != id {
|
||||
continue
|
||||
}
|
||||
return product
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewInMemoryCartStore создаёт новый менеджер объектов корзины
|
||||
func NewInMemoryCartStore() *InMemoryCartStore { return &InMemoryCartStore{mutex: sync.RWMutex{}} }
|
||||
|
||||
// Add добавляет новый объект в корзину.
|
||||
//
|
||||
// * Если объекта не существует, то он будет добавлен в список.
|
||||
// * Если объект уже существует в корзине, то его количество в корзине будет увеличено.
|
||||
// * Ошибка будет возвращена если не был указан ID продукта или его количество равно или меньше ноля.
|
||||
//
|
||||
// BUG(toby3d): InMemoryStore не проверяет наличие продукта по его ID, подразумевая, что он всегда существует в базе.
|
||||
func (ims *InMemoryCartStore) Add(i *model.Item) error {
|
||||
switch {
|
||||
case i == nil, i.GetProductId() <= 0:
|
||||
return ErrNoProductId
|
||||
case i.GetQuanity() <= 0:
|
||||
return ErrZeroQuanity
|
||||
}
|
||||
|
||||
if item := ims.GetById(i.GetProductId()); item != nil {
|
||||
ims.mutex.Lock()
|
||||
item.Quanity += i.GetQuanity()
|
||||
ims.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
ims.mutex.Lock()
|
||||
ims.items = append(ims.items, &model.Item{
|
||||
ProductId: i.GetProductId(),
|
||||
Quanity: i.GetQuanity(),
|
||||
})
|
||||
sort.Slice(ims.items, func(i, j int) bool {
|
||||
return ims.items[i].GetProductId() < ims.items[j].GetProductId()
|
||||
})
|
||||
ims.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetById возвращает объект корзины по ID его продукта (если существует).
|
||||
func (ims *InMemoryCartStore) GetById(id uint64) *model.Item {
|
||||
if id == 0 {
|
||||
return nil
|
||||
}
|
||||
ims.mutex.RLock()
|
||||
defer ims.mutex.RUnlock()
|
||||
for _, item := range ims.items {
|
||||
if item.GetProductId() != id {
|
||||
continue
|
||||
}
|
||||
return item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetList возвращает массив всех доступных в корзине объектов.
|
||||
func (ims *InMemoryCartStore) GetList() (int, []*model.Item) {
|
||||
ims.mutex.RLock()
|
||||
defer ims.mutex.RUnlock()
|
||||
return len(ims.items), ims.items
|
||||
}
|
||||
|
||||
// Update обновляет конкретный объект в корзине.
|
||||
//
|
||||
// * Если объекта с указанным ProductId ещё не существует в корзине, то он будет добавлен.
|
||||
// * Если объект уже существует в корзине, то его количество будет перезаписано.
|
||||
// * Если желаемое количество объекта равна нулю или отрицательно, то объект будет удалён из корзины.
|
||||
func (ims *InMemoryCartStore) Update(i *model.Item) error {
|
||||
if item := ims.GetById(i.GetProductId()); item != nil {
|
||||
if i.GetQuanity() <= 0 {
|
||||
return ims.Delete(i.GetProductId())
|
||||
}
|
||||
|
||||
ims.mutex.Lock()
|
||||
item.Quanity = i.GetQuanity()
|
||||
ims.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
return ims.Add(i)
|
||||
}
|
||||
|
||||
// Delete удаляет объект из корзины по его ProductId (если он существует).
|
||||
func (ims *InMemoryCartStore) Delete(id uint64) error {
|
||||
if item := ims.GetById(id); item == nil {
|
||||
return ErrNotExist
|
||||
}
|
||||
ims.mutex.Lock()
|
||||
defer ims.mutex.Unlock()
|
||||
for i := range ims.items {
|
||||
if ims.items[i].GetProductId() != id {
|
||||
continue
|
||||
}
|
||||
// NOTE(toby3d): см. https://github.com/golang/go/wiki/SliceTricks
|
||||
ims.items[i] = ims.items[len(ims.items)-1]
|
||||
ims.items[len(ims.items)-1] = nil
|
||||
ims.items = ims.items[:len(ims.items)-1]
|
||||
break
|
||||
}
|
||||
sort.Slice(ims.items, func(i, j int) bool {
|
||||
return ims.items[i].GetProductId() < ims.items[j].GetProductId()
|
||||
})
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
)
|
||||
|
||||
func TestInMemoryAdd(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 24}
|
||||
itemTwo := model.Item{ProductId: 24, Quanity: 42}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
assert.Error(t, s.Add(&model.Item{}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list, 0)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("zero quanity", func(t *testing.T) {
|
||||
assert.Error(t, s.Add(&model.Item{ProductId: 42}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list, 0)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("single", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("add same", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.NoError(t, s.Add(&model.Item{ProductId: itemOne.ProductId, Quanity: 6}))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Len(t, list, 1)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Contains(t, list, &model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.Quanity + 6,
|
||||
})
|
||||
})
|
||||
t.Run("add different", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.NoError(t, s.Add(&itemTwo))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Len(t, list, 2)
|
||||
assert.Equal(t, 2, count)
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Contains(t, list, &itemTwo)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestInMemoryGetById(t *testing.T) {
|
||||
t.Run("product", func(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
s := NewInMemoryProductStore()
|
||||
assert.Nil(t, s.GetById(0))
|
||||
})
|
||||
t.Run("not exist", func(t *testing.T) {
|
||||
s := NewInMemoryProductStore()
|
||||
s.Products = append(s.Products, &model.Product{Id: 42, Name: "Apple", Price: 4.99})
|
||||
assert.Nil(t, s.GetById(24))
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
s := NewInMemoryProductStore()
|
||||
productOne := model.Product{Id: 42, Name: "Apple", Price: 4.99}
|
||||
productTwo := model.Product{Id: 24, Name: "Banana", Price: 2.49}
|
||||
s.Products = append(s.Products, &productOne)
|
||||
s.Products = append(s.Products, &productTwo)
|
||||
assert.Equal(t, &productOne, s.GetById(productOne.GetId()))
|
||||
assert.Equal(t, &productTwo, s.GetById(productTwo.GetId()))
|
||||
})
|
||||
})
|
||||
t.Run("cart", func(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.Nil(t, s.GetById(0))
|
||||
})
|
||||
t.Run("not exist", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&model.Item{ProductId: 42, Quanity: 4}))
|
||||
assert.Nil(t, s.GetById(24))
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 4}
|
||||
itemTwo := model.Item{ProductId: 24, Quanity: 6}
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
assert.NoError(t, s.Add(&itemTwo))
|
||||
assert.Equal(t, &itemOne, s.GetById(itemOne.GetProductId()))
|
||||
assert.Equal(t, &itemTwo, s.GetById(itemTwo.GetProductId()))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestInMemoryGetList(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 4}
|
||||
itemTwo := model.Item{ProductId: 24, Quanity: 16}
|
||||
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Len(t, list, 1)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Contains(t, list, &itemOne)
|
||||
|
||||
assert.NoError(t, s.Add(&itemTwo))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Len(t, list, 2)
|
||||
assert.Equal(t, 2, count)
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Contains(t, list, &itemTwo)
|
||||
assert.Equal(t, list[0], &itemTwo, "list must be sorted by product_id")
|
||||
assert.Equal(t, list[1], &itemOne, "list must be sorted by product_id")
|
||||
}
|
||||
|
||||
func TestInMemoryUpdate(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 16}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.Error(t, s.Update(&model.Item{}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("zero quanity", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.Error(t, s.Update(&model.Item{ProductId: itemOne.ProductId}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("create", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
|
||||
assert.NoError(t, s.Update(&itemOne))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("change", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: 7,
|
||||
}))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Len(t, list, 1, "length of items store must not be changed")
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Contains(t, list, &model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: 7,
|
||||
})
|
||||
})
|
||||
t.Run("remove", func(t *testing.T) {
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.Quanity * 0,
|
||||
}))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("less than quanity", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.Quanity * -1,
|
||||
}))
|
||||
count, list = s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestInMemoryDelete(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 4}
|
||||
itemTwo := model.Item{ProductId: 24, Quanity: 15}
|
||||
itemThree := model.Item{ProductId: 420, Quanity: 7}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
assert.Error(t, s.Delete(itemTwo.ProductId))
|
||||
|
||||
count, list = s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
s := NewInMemoryCartStore()
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
assert.NoError(t, s.Add(&itemTwo))
|
||||
assert.NoError(t, s.Add(&itemThree))
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Contains(t, list, &itemTwo)
|
||||
assert.Contains(t, list, &itemThree)
|
||||
assert.Equal(t, count, 3)
|
||||
|
||||
assert.NoError(t, s.Delete(itemThree.ProductId))
|
||||
count, list = s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Contains(t, list, &itemTwo)
|
||||
assert.NotContains(t, list, &itemThree)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
)
|
||||
|
||||
type (
|
||||
CartStore struct{ conn *sqlx.DB }
|
||||
ProductStore struct{ conn *sqlx.DB }
|
||||
|
||||
itemResult struct {
|
||||
ProductID uint64 `db:"product_id"`
|
||||
Quanity int32 `db:"quanity"`
|
||||
}
|
||||
|
||||
productResult struct {
|
||||
ID uint64 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Price float32 `db:"price"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewCartStore(conn *sqlx.DB) *CartStore { return &CartStore{conn: conn} }
|
||||
|
||||
func NewProductStore(conn *sqlx.DB) *ProductStore { return &ProductStore{conn: conn} }
|
||||
|
||||
func (s *CartStore) Add(item *model.Item) (err error) {
|
||||
switch {
|
||||
case item == nil, item.GetProductId() <= 0:
|
||||
return ErrNoProductId
|
||||
case item.GetQuanity() <= 0:
|
||||
return ErrZeroQuanity
|
||||
}
|
||||
|
||||
// NOTE(toby3d): Сначала проверяем существование продукта, который мы хотим добавить в корзину
|
||||
var p productResult
|
||||
if err = s.conn.Get(&p, `SELECT * FROM products WHERE id = $1`, item.GetProductId()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE(toby3d): возможно продукт уже в корзине и мы просто хотим увеличить его количество
|
||||
if i := s.GetById(item.ProductId); i != nil { // NOTE(toby3d): продукт уже в корзине, увеличиваем
|
||||
i.Quanity += item.Quanity
|
||||
_, err = s.conn.Exec(`UPDATE cart SET quanity = $2 WHERE product_id = $1`, p.ID, i.GetQuanity())
|
||||
} else { // NOTE(toby3d): объекта в корзине ещё нет, добавляем
|
||||
_, err = s.conn.Exec(
|
||||
"INSERT INTO cart (product_id, quanity) VALUES ($1, $2)", p.ID, item.GetQuanity(),
|
||||
)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *CartStore) Delete(id uint64) (err error) {
|
||||
_, err = s.conn.Exec(`DELETE FROM cart WHERE product_id = $1`, id)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *CartStore) GetById(id uint64) *model.Item {
|
||||
var item itemResult
|
||||
if err := s.conn.Get(&item, "SELECT * FROM cart WHERE product_id=$1", id); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &model.Item{
|
||||
ProductId: item.ProductID,
|
||||
Quanity: item.Quanity,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CartStore) GetList() (int, []*model.Item) {
|
||||
rows, err := s.conn.Queryx(`SELECT * FROM cart ORDER BY product_id ASC`)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []*model.Item
|
||||
for rows.Next() {
|
||||
var item itemResult
|
||||
if err = rows.StructScan(&item); err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, &model.Item{
|
||||
ProductId: item.ProductID,
|
||||
Quanity: item.Quanity,
|
||||
})
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return len(items), items
|
||||
}
|
||||
|
||||
func (s *CartStore) Update(item *model.Item) (err error) {
|
||||
if i := s.GetById(item.GetProductId()); i != nil {
|
||||
if item.GetQuanity() <= 0 {
|
||||
return s.Delete(i.GetProductId())
|
||||
}
|
||||
|
||||
_, err = s.conn.Exec(`UPDATE cart SET quanity = $2 WHERE product_id = $1`, i.GetProductId(), item.GetQuanity())
|
||||
return err
|
||||
}
|
||||
return s.Add(item)
|
||||
}
|
||||
|
||||
func (s *ProductStore) GetById(id uint64) *model.Product {
|
||||
var product productResult
|
||||
if err := s.conn.Get(&product, "SELECT * FROM products WHERE id=$1", id); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &model.Product{
|
||||
Id: product.ID,
|
||||
Name: product.Name,
|
||||
Price: product.Price,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/toby3d/test/internal/model"
|
||||
)
|
||||
|
||||
func newDataBase(t *testing.T) (*sqlx.DB, sqlmock.Sqlmock, func()) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if !assert.NoError(t, err) {
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
return sqlx.NewDb(db, "sqlmock"), mock, func() { assert.NoError(t, mock.ExpectationsWereMet()) }
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
|
||||
s := NewCartStore(db)
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 24}
|
||||
itemTwo := model.Item{ProductId: 24, Quanity: 42}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
assert.Error(t, s.Add(&model.Item{}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list, 0)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("zero quanity", func(t *testing.T) {
|
||||
assert.Error(t, s.Add(&model.Item{ProductId: 42}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list, 0)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("not exists product", func(t *testing.T) {
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
assert.Error(t, s.Add(&model.Item{ProductId: 420, Quanity: 7}))
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("single", func(t *testing.T) {
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WillReturnRows(
|
||||
mock.NewRows([]string{"id", "name", "price"}).
|
||||
AddRow(itemOne.GetProductId(), "Apple", 4.99),
|
||||
)
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`)
|
||||
mock.ExpectExec(`INSERT INTO cart`).
|
||||
WithArgs(itemOne.GetProductId(), itemOne.GetQuanity()).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Add(&itemOne))
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("append", func(t *testing.T) {
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WillReturnRows(
|
||||
mock.NewRows([]string{"id", "name", "price"}).
|
||||
AddRow(itemOne.GetProductId(), "Apple", 4.99),
|
||||
)
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
mock.ExpectExec(`UPDATE cart SET quanity`).
|
||||
WithArgs(itemOne.GetProductId(), itemOne.GetQuanity()+6).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Add(&model.Item{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: 6,
|
||||
}))
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()+6),
|
||||
)
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Contains(t, list, &model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.GetQuanity() + 6,
|
||||
})
|
||||
})
|
||||
t.Run("add different", func(t *testing.T) {
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WillReturnRows(
|
||||
mock.NewRows([]string{"id", "name", "price"}).
|
||||
AddRow(itemTwo.GetProductId(), "Banana", 2.49),
|
||||
)
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`)
|
||||
mock.ExpectExec(`INSERT INTO cart`).
|
||||
WithArgs(itemTwo.GetProductId(), itemTwo.GetQuanity()).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemTwo.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Add(&itemTwo))
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()).
|
||||
AddRow(itemTwo.GetProductId(), itemTwo.GetQuanity()),
|
||||
)
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Equal(t, 2, count)
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Contains(t, list, &itemTwo)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
mock.ExpectExec(`DELETE FROM cart WHERE product_id`).WithArgs(240)
|
||||
assert.Error(t, NewCartStore(db).Delete(240))
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
mock.ExpectExec(`DELETE FROM cart WHERE product_id`).WithArgs(42).
|
||||
WillReturnResult(sqlmock.NewResult(42, 1))
|
||||
assert.NoError(t, NewCartStore(db).Delete(42))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetById(t *testing.T) {
|
||||
t.Run("cart", func(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).WithArgs(24).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
assert.Nil(t, NewCartStore(db).GetById(24))
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).WithArgs(42).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).AddRow(42, 24))
|
||||
|
||||
assert.Equal(t, &model.Item{
|
||||
ProductId: 42,
|
||||
Quanity: 24,
|
||||
}, NewCartStore(db).GetById(42))
|
||||
})
|
||||
})
|
||||
t.Run("product", func(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WithArgs(24).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
assert.Nil(t, NewProductStore(db).GetById(24))
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WithArgs(42).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{"id", "name", "price"}).AddRow(42, "Apple", 4.99),
|
||||
)
|
||||
|
||||
assert.Equal(t, &model.Product{
|
||||
Id: 42,
|
||||
Name: "Apple",
|
||||
Price: 4.99,
|
||||
}, NewProductStore(db).GetById(42))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetList(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
mock.ExpectQuery(`SELECT \* FROM cart ORDER BY product_id ASC`).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(24, 7).
|
||||
AddRow(420, 11).
|
||||
AddRow(42, 24),
|
||||
)
|
||||
|
||||
count, list := NewCartStore(db).GetList()
|
||||
assert.Equal(t, 3, count)
|
||||
assert.Contains(t, list, &model.Item{ProductId: 24, Quanity: 7})
|
||||
assert.Contains(t, list, &model.Item{ProductId: 42, Quanity: 24})
|
||||
assert.Contains(t, list, &model.Item{ProductId: 420, Quanity: 11})
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
itemOne := model.Item{ProductId: 42, Quanity: 16}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
db, _, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
assert.Error(t, s.Update(&model.Item{}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("zero quanity", func(t *testing.T) {
|
||||
db, _, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
assert.Error(t, s.Update(&model.Item{ProductId: itemOne.ProductId}))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("non exists product", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
assert.Error(t, s.Update(&itemOne))
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("create", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`)
|
||||
mock.ExpectQuery(`SELECT \* FROM products WHERE id`).WillReturnRows(
|
||||
mock.NewRows([]string{"id", "name", "price"}).
|
||||
AddRow(itemOne.GetProductId(), "Apple", 4.99),
|
||||
)
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`)
|
||||
mock.ExpectExec(`INSERT INTO cart`).
|
||||
WithArgs(itemOne.GetProductId(), itemOne.GetQuanity()).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Update(&itemOne))
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Contains(t, list, &itemOne)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
t.Run("change", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
mock.ExpectExec(`UPDATE cart SET quanity`).
|
||||
WithArgs(itemOne.GetProductId(), 7).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: 7,
|
||||
}))
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), 7),
|
||||
)
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Len(t, list, 1, "length of items store must not be changed")
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Contains(t, list, &model.Item{
|
||||
ProductId: itemOne.GetProductId(),
|
||||
Quanity: 7,
|
||||
})
|
||||
})
|
||||
t.Run("remove", func(t *testing.T) {
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
mock.ExpectExec(`DELETE FROM cart WHERE product_id`).WithArgs(itemOne.GetProductId()).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.Quanity * 0,
|
||||
}))
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
t.Run("less than quanity", func(t *testing.T) {
|
||||
db, mock, release := newDataBase(t)
|
||||
defer release()
|
||||
s := NewCartStore(db)
|
||||
|
||||
mock.ExpectQuery(`SELECT \* FROM cart WHERE product_id`).
|
||||
WillReturnRows(mock.NewRows([]string{"product_id", "quanity"}).
|
||||
AddRow(itemOne.GetProductId(), itemOne.GetQuanity()),
|
||||
)
|
||||
mock.ExpectExec(`DELETE FROM cart WHERE product_id`).WithArgs(itemOne.GetProductId()).
|
||||
WillReturnResult(sqlmock.NewResult(int64(itemOne.GetProductId()), 1))
|
||||
|
||||
assert.NoError(t, s.Update(&model.Item{
|
||||
ProductId: itemOne.ProductId,
|
||||
Quanity: itemOne.Quanity * -1,
|
||||
}))
|
||||
|
||||
count, list := s.GetList()
|
||||
assert.Empty(t, list)
|
||||
assert.Zero(t, count)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue