[hw-2] add tests, coverage, benchmarks

This commit is contained in:
Никита Шубин
2025-05-31 10:47:47 +00:00
parent 1a3d4892a8
commit a0e36639ca
22 changed files with 3058 additions and 48 deletions

View File

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog/log"
)
//go:generate minimock -i Repository -o ./mock -s _mock.go
type Repository interface {
AddItem(ctx context.Context, userID entity.UID, item *model.Item) error
GetItemsByUserID(ctx context.Context, userID entity.UID) (entity.Cart, error)
@@ -87,7 +88,10 @@ func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) (
errCh := make(chan error, 1)
var wg sync.WaitGroup
var (
wg sync.WaitGroup
sumMutex sync.Mutex
)
for idx, sku := range cart.Items {
wg.Add(1)
@@ -111,7 +115,10 @@ func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) (
Product: product,
Count: count,
}
sumMutex.Lock()
resultCart.TotalPrice += uint32(product.Price) * count
sumMutex.Unlock()
}(sku, cart.ItemCount[sku], idx)
}

View File

@@ -0,0 +1,575 @@
package service
import (
"context"
"errors"
"testing"
"github.com/gojuno/minimock/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"route256/cart/internal/domain/cart/service/mock"
"route256/cart/internal/domain/entity"
"route256/cart/internal/domain/model"
)
const (
validPrice = 123
validName = "some product name"
)
type ProductServiceFake struct{}
func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) {
if sku%2 == 0 {
return nil, errors.New("empty shelf")
}
return &model.Product{
Name: validName,
Price: validPrice,
Sku: sku,
}, nil
}
func TestCartService_AddItem(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testSKU := entity.Sku(199)
testItem := model.Item{
Product: &model.Product{
Name: validName,
Price: 123,
Sku: testSKU,
},
Count: 1,
}
type fields struct {
repository Repository
productService ProductService
}
type args struct {
ctx context.Context
item *model.Item
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
AddItemMock.
Expect(ctx, 1337, &testItem).
Return(nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
item: &testItem,
userID: 1337,
},
wantErr: require.NoError,
},
{
name: "invalid item",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
item: &model.Item{
Product: &model.Product{
Name: "name",
Price: 123,
Sku: 0,
},
Count: 0,
},
userID: 0,
},
wantErr: require.Error,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
item: &testItem,
userID: 0,
},
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: nil,
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
item: &model.Item{
Product: &model.Product{
Name: "",
Price: 0,
Sku: 4,
},
Count: 1,
},
userID: 1337,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
AddItemMock.
Expect(ctx, 1337, &testItem).
Return(assert.AnError),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
item: &testItem,
userID: 1337,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
productService: tt.fields.productService,
}
err := s.AddItem(tt.args.ctx, tt.args.userID, tt.args.item)
tt.wantErr(t, err, "check add review error")
})
}
}
func TestCartService_GetItemsByUserID(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testUID := entity.UID(1337)
testSKU := entity.Sku(199)
testModelCart := model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 1,
},
},
TotalPrice: validPrice,
}
testEntityCart := entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU},
ItemCount: map[entity.Sku]uint32{testSKU: 1},
}
type fields struct {
repository Repository
productService ProductService
}
type args struct {
ctx context.Context
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
want *model.Cart
wantErr require.ErrorAssertionFunc
}{
{
name: "success 1 item",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(testEntityCart, nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &testModelCart,
wantErr: require.NoError,
},
{
name: "success 2 items",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU},
ItemCount: map[entity.Sku]uint32{testSKU: 2},
}, nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 2,
},
},
TotalPrice: validPrice * 2,
},
wantErr: require.NoError,
},
{
name: "success 2 different items",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{testSKU, 1},
ItemCount: map[entity.Sku]uint32{testSKU: 1, 1: 1},
}, nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: &model.Cart{
UserID: testUID,
Items: []*model.Item{
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: 1,
},
Count: 1,
},
{
Product: &model.Product{
Name: validName,
Price: validPrice,
Sku: testSKU,
},
Count: 1,
},
},
TotalPrice: validPrice * 2,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
productService: nil,
},
args: args{
ctx: ctx,
userID: 0,
},
want: nil,
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{}, assert.AnError),
productService: nil,
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "empty cart",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{}, nil),
productService: nil,
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{2},
ItemCount: map[entity.Sku]uint32{2: 1},
}, nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
{
name: "product service error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
GetItemsByUserIDMock.
Expect(ctx, testUID).
Return(entity.Cart{
UserID: testUID,
Items: []entity.Sku{2},
ItemCount: map[entity.Sku]uint32{2: 1},
}, nil),
productService: &ProductServiceFake{},
},
args: args{
ctx: ctx,
userID: testUID,
},
want: nil,
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
productService: tt.fields.productService,
}
got, err := s.GetItemsByUserID(tt.args.ctx, tt.args.userID)
tt.wantErr(t, err, "check add review error")
assert.True(t, assert.EqualExportedValues(t, tt.want, got), "got unexpected cart")
})
}
}
func TestCartService_DeleteItem(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testSKU := entity.Sku(199)
testUID := entity.UID(1337)
type fields struct {
repository Repository
}
type args struct {
ctx context.Context
userID entity.UID
sku entity.Sku
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemMock.
Expect(ctx, testUID, testSKU).
Return(nil),
},
args: args{
ctx: ctx,
userID: testUID,
sku: testSKU,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: 0,
sku: testSKU,
},
wantErr: require.Error,
},
{
name: "invalid sku",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: testUID,
sku: 0,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemMock.
Expect(ctx, testUID, testSKU).
Return(assert.AnError),
},
args: args{
ctx: ctx,
sku: testSKU,
userID: testUID,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
}
err := s.DeleteItem(tt.args.ctx, tt.args.userID, tt.args.sku)
tt.wantErr(t, err, "check add review error")
})
}
}
func TestCartService_DeleteItemsByUserID(t *testing.T) {
t.Parallel()
ctx := context.Background()
mc := minimock.NewController(t)
testUID := entity.UID(1337)
type fields struct {
repository Repository
}
type args struct {
ctx context.Context
userID entity.UID
}
tests := []struct {
name string
fields fields
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "success",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemsByUserIDMock.
Expect(ctx, testUID).
Return(nil),
},
args: args{
ctx: ctx,
userID: testUID,
},
wantErr: require.NoError,
},
{
name: "invalid user id",
fields: fields{
repository: nil,
},
args: args{
ctx: ctx,
userID: 0,
},
wantErr: require.Error,
},
{
name: "repository error",
fields: fields{
repository: mock.NewRepositoryMock(mc).
DeleteItemsByUserIDMock.
Expect(ctx, testUID).
Return(assert.AnError),
},
args: args{
ctx: ctx,
userID: testUID,
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
s := &CartService{
repository: tt.fields.repository,
}
err := s.DeleteItemsByUserID(tt.args.ctx, tt.args.userID)
tt.wantErr(t, err, "check add review error")
})
}
}