mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 22:13:44 +03:00
[hw-3] loms service
This commit is contained in:
118
cart/internal/domain/repository/in_memory_repository.go
Normal file
118
cart/internal/domain/repository/in_memory_repository.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
)
|
||||
|
||||
type storage = map[entity.UID]*entity.Cart
|
||||
|
||||
type InMemoryRepository struct {
|
||||
storage storage
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryRepository(cap int) *InMemoryRepository {
|
||||
return &InMemoryRepository{
|
||||
storage: make(storage, cap),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) AddItem(_ context.Context, userID entity.UID, item *model.Item) error {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
cart, ok := r.storage[userID]
|
||||
if !ok {
|
||||
cart = &entity.Cart{
|
||||
UserID: userID,
|
||||
Items: []entity.Sku{},
|
||||
ItemCount: map[entity.Sku]uint32{},
|
||||
}
|
||||
|
||||
r.storage[userID] = cart
|
||||
}
|
||||
|
||||
if _, ok := cart.ItemCount[item.Product.Sku]; !ok {
|
||||
cart.Items = append(cart.Items, item.Product.Sku)
|
||||
}
|
||||
|
||||
cart.ItemCount[item.Product.Sku] += item.Count
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) GetItemsByUserID(_ context.Context, userID entity.UID) (entity.Cart, error) {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
cart, ok := r.storage[userID]
|
||||
if !ok {
|
||||
return entity.Cart{}, nil
|
||||
}
|
||||
|
||||
resultCart := entity.Cart{
|
||||
UserID: userID,
|
||||
Items: make([]entity.Sku, len(cart.Items)),
|
||||
ItemCount: make(map[entity.Sku]uint32, len(cart.ItemCount)),
|
||||
}
|
||||
|
||||
for i, sku := range cart.Items {
|
||||
resultCart.Items[i] = cart.Items[i]
|
||||
resultCart.ItemCount[sku] = cart.ItemCount[sku]
|
||||
}
|
||||
|
||||
return resultCart, nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) DeleteItem(_ context.Context, userID entity.UID, sku entity.Sku) error {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
cart, ok := r.storage[userID]
|
||||
if !ok {
|
||||
return model.ErrCartNotFound
|
||||
}
|
||||
|
||||
if len(cart.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(cart.ItemCount, sku)
|
||||
|
||||
i := 0
|
||||
found := false
|
||||
for _, v := range cart.Items {
|
||||
if v == sku {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if !found {
|
||||
return model.ErrItemNotFoundInCart
|
||||
}
|
||||
|
||||
cart.Items[i] = cart.Items[len(cart.Items)-1]
|
||||
cart.Items = cart.Items[:len(cart.Items)-1]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) DeleteItemsByUserID(_ context.Context, userID entity.UID) error {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
_, ok := r.storage[userID]
|
||||
if ok {
|
||||
delete(r.storage, userID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
testItem = &model.Item{
|
||||
Product: &model.Product{
|
||||
Sku: 1,
|
||||
Name: "bench",
|
||||
Price: 100,
|
||||
},
|
||||
Count: 1,
|
||||
}
|
||||
)
|
||||
|
||||
func BenchmarkInMemoryRepository_AddItem(b *testing.B) {
|
||||
repo := NewInMemoryRepository(1024)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 1; i < b.N; i++ {
|
||||
uid := entity.UID(i % 1000000)
|
||||
_ = repo.AddItem(ctx, uid, testItem)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInMemoryRepository_AddItemParallel(b *testing.B) {
|
||||
repo := NewInMemoryRepository(1024)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var i int
|
||||
for pb.Next() {
|
||||
uid := entity.UID(i % 1000000)
|
||||
_ = repo.AddItem(ctx, uid, testItem)
|
||||
i++
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkInMemoryRepository_DeleteItem(b *testing.B) {
|
||||
repo := NewInMemoryRepository(1024)
|
||||
|
||||
for i := 1; i < 1_000; i++ {
|
||||
_ = repo.AddItem(ctx, entity.UID(i), testItem)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 1; i < b.N; i++ {
|
||||
uid := entity.UID(i % 1000000)
|
||||
_ = repo.DeleteItem(ctx, uid, entity.Sku(testItem.Product.Sku))
|
||||
}
|
||||
}
|
||||
269
cart/internal/domain/repository/in_memory_repository_test.go
Normal file
269
cart/internal/domain/repository/in_memory_repository_test.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"route256/cart/internal/domain/entity"
|
||||
"route256/cart/internal/domain/model"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
item = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: sku,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[sku], "check count value")
|
||||
assert.Equal(t, uid, repo.storage[uid].UserID, "check uid")
|
||||
}
|
||||
|
||||
func TestAddItemMultiple(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
item1 = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: 1,
|
||||
},
|
||||
Count: 1,
|
||||
}
|
||||
item2 = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: 2,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item1)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
err = repo.AddItem(ctx, uid, item2)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 2, "check list length")
|
||||
assert.Equal(t, uint32(1), repo.storage[uid].ItemCount[1], "check count value of item 1")
|
||||
assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[2], "check count value of item 2")
|
||||
assert.Equal(t, uid, repo.storage[uid].UserID, "check uid")
|
||||
}
|
||||
|
||||
func TestGetItemsByUserID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
item1 = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: 1,
|
||||
},
|
||||
Count: 1,
|
||||
}
|
||||
item2 = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: 2,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item1)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
err = repo.AddItem(ctx, uid, item2)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
cart, err := repo.GetItemsByUserID(ctx, uid)
|
||||
require.NoError(t, err, "check get items error")
|
||||
|
||||
require.Equal(t, uid, cart.UserID, "check cart uid")
|
||||
require.Len(t, cart.Items, 2, "check cart len")
|
||||
require.Equal(t, uint32(1), cart.ItemCount[item1.Product.Sku], "check item 1 count")
|
||||
require.Equal(t, uint32(2), cart.ItemCount[item2.Product.Sku], "check item 2 count")
|
||||
}
|
||||
|
||||
func TestGetItemsByUserIDNoCart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
cart, err := repo.GetItemsByUserID(ctx, uid)
|
||||
require.NoError(t, err, "check get items error")
|
||||
|
||||
require.Equal(t, entity.Cart{}, cart, "check cart")
|
||||
}
|
||||
|
||||
func TestDeleteItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
item = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: sku,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
|
||||
err = repo.DeleteItem(ctx, uid, item.Product.Sku)
|
||||
require.NoError(t, err, "check delete item error")
|
||||
|
||||
assert.Len(t, repo.storage[uid].Items, 0, "check list length")
|
||||
}
|
||||
|
||||
func TestDeleteItemUnknownUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.DeleteItem(ctx, uid, sku)
|
||||
require.ErrorIs(t, err, model.ErrCartNotFound, "check cart not found err")
|
||||
}
|
||||
|
||||
func TestDeleteItemUnknownItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
item = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: sku,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
|
||||
err = repo.DeleteItem(ctx, uid, 1)
|
||||
require.ErrorIs(t, err, model.ErrItemNotFoundInCart, "check delete item error")
|
||||
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
}
|
||||
|
||||
func TestDeleteItemEmptyCart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
item = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: sku,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
|
||||
err = repo.DeleteItem(ctx, uid, item.Product.Sku)
|
||||
require.NoError(t, err, "check delete item error")
|
||||
|
||||
assert.Len(t, repo.storage[uid].Items, 0, "check list length")
|
||||
|
||||
err = repo.DeleteItem(ctx, uid, item.Product.Sku)
|
||||
require.NoError(t, err, "check empty cart no err")
|
||||
}
|
||||
|
||||
func TestDeleteItemsByUserID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
repo = NewInMemoryRepository(10)
|
||||
uid = entity.UID(1337)
|
||||
sku = entity.Sku(123)
|
||||
item = &model.Item{
|
||||
Product: &model.Product{
|
||||
Name: "",
|
||||
Price: 0,
|
||||
Sku: sku,
|
||||
},
|
||||
Count: 2,
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := repo.AddItem(ctx, uid, item)
|
||||
require.NoError(t, err, "check add item error")
|
||||
|
||||
assert.Len(t, repo.storage, 1, "check map length")
|
||||
assert.Len(t, repo.storage[uid].Items, 1, "check list length")
|
||||
|
||||
err = repo.DeleteItemsByUserID(ctx, uid)
|
||||
require.NoError(t, err, "check delete items error")
|
||||
|
||||
assert.Len(t, repo.storage, 0, "check storage length")
|
||||
}
|
||||
Reference in New Issue
Block a user