mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 05:53:45 +03:00
337 lines
8.8 KiB
Go
337 lines
8.8 KiB
Go
//go:build integration
|
||
// +build integration
|
||
|
||
package integration
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"net"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/jackc/pgx/v5/pgxpool"
|
||
_ "github.com/jackc/pgx/v5/stdlib"
|
||
"github.com/ozontech/allure-go/pkg/framework/provider"
|
||
"github.com/ozontech/allure-go/pkg/framework/suite"
|
||
"github.com/pressly/goose/v3"
|
||
"github.com/testcontainers/testcontainers-go"
|
||
"github.com/testcontainers/testcontainers-go/wait"
|
||
"google.golang.org/grpc"
|
||
"google.golang.org/grpc/codes"
|
||
"google.golang.org/grpc/credentials/insecure"
|
||
"google.golang.org/grpc/status"
|
||
|
||
"route256/loms/internal/app/server"
|
||
"route256/loms/internal/domain/entity"
|
||
ordersRepository "route256/loms/internal/domain/repository/orders/sqlc"
|
||
stocksRepository "route256/loms/internal/domain/repository/stocks/sqlc"
|
||
lomsService "route256/loms/internal/domain/service"
|
||
"route256/loms/internal/infra/postgres"
|
||
|
||
pb "route256/pkg/api/loms/v1"
|
||
)
|
||
|
||
const (
|
||
// "total_count": 100,
|
||
// "reserved": 35
|
||
testSKU = entity.Sku(1076963)
|
||
|
||
testUID = entity.ID(1337)
|
||
testCount = uint32(2)
|
||
migrationsDir = "../../db/migrations"
|
||
)
|
||
|
||
// TODO: drop, use actual kafka for tests.
|
||
type mockKafkaProducer struct{}
|
||
|
||
func (kp mockKafkaProducer) Send(_ context.Context, id entity.ID, status string) error {
|
||
return nil
|
||
}
|
||
|
||
func startPostgres(ctx context.Context, migrationsDir string) (*pgxpool.Pool, func(), error) {
|
||
req := testcontainers.ContainerRequest{
|
||
Image: "gitlab-registry.ozon.dev/go/classroom-18/students/base/postgres:16",
|
||
Env: map[string]string{
|
||
"POSTGRESQL_USERNAME": "user",
|
||
"POSTGRESQL_PASSWORD": "postgres",
|
||
"POSTGRESQL_DATABASE": "loms_test",
|
||
},
|
||
ExposedPorts: []string{"5432/tcp"},
|
||
WaitingFor: wait.ForListeningPort("5432/tcp").WithStartupTimeout(30 * time.Second),
|
||
}
|
||
container, err := testcontainers.GenericContainer(ctx,
|
||
testcontainers.GenericContainerRequest{ContainerRequest: req, Started: true})
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
endpoint, err := container.PortEndpoint(ctx, "5432", "")
|
||
if err != nil {
|
||
container.Terminate(ctx)
|
||
return nil, nil, err
|
||
}
|
||
|
||
dsn := fmt.Sprintf("postgresql://user:postgres@%s/loms_test?sslmode=disable", endpoint)
|
||
|
||
var pool *pgxpool.Pool
|
||
for i := 0; i < 30; i++ {
|
||
pool, err = pgxpool.New(ctx, dsn)
|
||
if err == nil && pool.Ping(ctx) == nil {
|
||
break
|
||
}
|
||
time.Sleep(1 * time.Second)
|
||
}
|
||
if err != nil {
|
||
container.Terminate(ctx)
|
||
return nil, nil, err
|
||
}
|
||
|
||
std, err := sql.Open("pgx", dsn)
|
||
if err != nil {
|
||
container.Terminate(ctx)
|
||
return nil, nil, err
|
||
}
|
||
|
||
if err := goose.Up(std, migrationsDir); err != nil {
|
||
container.Terminate(ctx)
|
||
return nil, nil, err
|
||
}
|
||
std.Close()
|
||
|
||
cleanup := func() {
|
||
pool.Close()
|
||
_ = container.Terminate(context.Background())
|
||
}
|
||
return pool, cleanup, nil
|
||
}
|
||
|
||
type LomsIntegrationSuite struct {
|
||
suite.Suite
|
||
|
||
grpcSrv *grpc.Server
|
||
grpcConn *grpc.ClientConn
|
||
|
||
lomsClient pb.LOMSClient
|
||
|
||
cleanup func()
|
||
}
|
||
|
||
func TestLomsIntegrationSuite(t *testing.T) {
|
||
suite.RunSuite(t, new(LomsIntegrationSuite))
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) BeforeAll(t provider.T) {
|
||
ctx := context.Background()
|
||
t.WithNewStep("init cart-service", func(sCtx provider.StepCtx) {
|
||
pool, cleanup, err := startPostgres(ctx, migrationsDir)
|
||
sCtx.Require().NoError(err, "failed postgres setup")
|
||
|
||
s.cleanup = cleanup
|
||
|
||
orderRepo := ordersRepository.NewOrderRepository(pool)
|
||
stockRepo := stocksRepository.NewStockRepository(pool, pool)
|
||
|
||
txManager := postgres.NewTxManager(pool, pool)
|
||
|
||
svc := lomsService.NewLomsService(orderRepo, stockRepo, txManager, &mockKafkaProducer{})
|
||
lomsServer := server.NewServer(svc)
|
||
|
||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||
sCtx.Require().NoError(err)
|
||
|
||
s.grpcSrv = grpc.NewServer()
|
||
pb.RegisterLOMSServer(s.grpcSrv, lomsServer)
|
||
|
||
go func() { _ = s.grpcSrv.Serve(lis) }()
|
||
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||
sCtx.Require().NoError(err)
|
||
|
||
s.grpcConn = conn
|
||
s.lomsClient = pb.NewLOMSClient(conn)
|
||
})
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) AfterAll(t provider.T) {
|
||
s.grpcSrv.Stop()
|
||
_ = s.grpcConn.Close()
|
||
s.cleanup()
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) TestOrderProcessPositive(t provider.T) {
|
||
ctx := context.Background()
|
||
|
||
var orderID int64
|
||
|
||
t.WithNewStep("create order", func(sCtx provider.StepCtx) {
|
||
req := &pb.OrderCreateRequest{
|
||
UserId: int64(testUID),
|
||
Items: []*pb.OrderItem{{
|
||
Sku: int64(testSKU),
|
||
Count: testCount,
|
||
}},
|
||
}
|
||
|
||
resp, err := s.lomsClient.OrderCreate(ctx, req)
|
||
sCtx.Require().NoError(err)
|
||
sCtx.Require().Greater(resp.OrderId, int64(0))
|
||
orderID = resp.OrderId
|
||
})
|
||
|
||
t.WithNewStep("verify order info (NEW)", func(sCtx provider.StepCtx) {
|
||
resp, err := s.lomsClient.OrderInfo(ctx, &pb.OrderInfoRequest{OrderId: orderID})
|
||
sCtx.Require().NoError(err)
|
||
|
||
sCtx.Require().Equal("awaiting payment", resp.Status)
|
||
sCtx.Require().Equal(int64(testUID), resp.UserId)
|
||
sCtx.Require().Len(resp.Items, 1)
|
||
sCtx.Require().Equal(int64(testSKU), resp.Items[0].Sku)
|
||
sCtx.Require().Equal(testCount, resp.Items[0].Count)
|
||
})
|
||
|
||
t.WithNewStep("pay order", func(sCtx provider.StepCtx) {
|
||
_, err := s.lomsClient.OrderPay(ctx, &pb.OrderPayRequest{OrderId: orderID})
|
||
sCtx.Require().NoError(err)
|
||
})
|
||
|
||
t.WithNewStep("verify order info (PAYED)", func(sCtx provider.StepCtx) {
|
||
resp, err := s.lomsClient.OrderInfo(ctx, &pb.OrderInfoRequest{OrderId: orderID})
|
||
sCtx.Require().NoError(err)
|
||
sCtx.Require().Equal("payed", resp.Status)
|
||
})
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) TestStocksInfoPositive(t provider.T) {
|
||
ctx := context.Background()
|
||
|
||
t.WithNewStep("call StocksInfo", func(sCtx provider.StepCtx) {
|
||
resp, err := s.lomsClient.StocksInfo(ctx, &pb.StocksInfoRequest{Sku: int64(testSKU)})
|
||
sCtx.Require().NoError(err)
|
||
sCtx.Require().Greater(resp.Count, uint32(0))
|
||
})
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) TestOrderCreate_SuccessAsync(t provider.T) {
|
||
t.Title("Успешное создание заказов (async)")
|
||
|
||
const (
|
||
sku = 1625903
|
||
count = 1
|
||
|
||
ordersCount = 100
|
||
)
|
||
|
||
var (
|
||
userIDs = []int64{42, 43, 44}
|
||
orders = make([]struct {
|
||
userID int64
|
||
orderID int64
|
||
}, ordersCount)
|
||
|
||
initStocksCount uint64
|
||
|
||
ctx = context.Background()
|
||
)
|
||
|
||
t.WithNewStep("Получение изначальных стоков", func(sCtx provider.StepCtx) {
|
||
stockCount, err := s.lomsClient.StocksInfo(ctx, &pb.StocksInfoRequest{Sku: sku})
|
||
sCtx.Require().NoError(err)
|
||
initStocksCount = uint64(stockCount.GetCount())
|
||
})
|
||
|
||
t.WithNewStep("Создание заказов", func(sCtx provider.StepCtx) {
|
||
var wg sync.WaitGroup
|
||
|
||
for i := range ordersCount {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
|
||
userID := userIDs[i%len(userIDs)]
|
||
|
||
req := &pb.OrderCreateRequest{
|
||
UserId: userID,
|
||
Items: []*pb.OrderItem{
|
||
{
|
||
Sku: sku,
|
||
Count: count,
|
||
},
|
||
},
|
||
}
|
||
orderCreateResp, err := s.lomsClient.OrderCreate(ctx, req)
|
||
sCtx.Require().NoError(err)
|
||
sCtx.Require().Greater(orderCreateResp.OrderId, int64(0))
|
||
|
||
orders[i].userID = userID
|
||
orders[i].orderID = orderCreateResp.OrderId
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
})
|
||
|
||
t.WithNewStep("Проверка заказов", func(sCtx provider.StepCtx) {
|
||
for _, order := range orders {
|
||
res, err := s.lomsClient.OrderInfo(ctx, &pb.OrderInfoRequest{
|
||
OrderId: order.orderID,
|
||
})
|
||
sCtx.Require().NoError(err)
|
||
|
||
expected := entity.Order{
|
||
Status: "awaiting payment",
|
||
UserID: entity.ID(order.userID),
|
||
Items: []entity.OrderItem{
|
||
{
|
||
ID: sku,
|
||
Count: count,
|
||
},
|
||
},
|
||
}
|
||
|
||
sCtx.Require().Equal(expected.Status, res.Status, "Не совпадает статус заказа")
|
||
sCtx.Require().Equal(expected.UserID, entity.ID(res.UserId), "Не совпадает пользователь заказа")
|
||
sCtx.Require().Equal(len(expected.Items), len(res.Items), "Не совпадает количество товаров в заказе")
|
||
}
|
||
})
|
||
|
||
t.WithNewStep("Проверка стоков", func(sCtx provider.StepCtx) {
|
||
stocksCount, err := s.lomsClient.StocksInfo(ctx, &pb.StocksInfoRequest{
|
||
Sku: sku,
|
||
})
|
||
sCtx.Require().NoError(err)
|
||
sCtx.Require().Equal(uint32(initStocksCount-ordersCount*count), stocksCount.Count)
|
||
})
|
||
}
|
||
|
||
func (s *LomsIntegrationSuite) TestOrderCreate_NoStockInfo(t provider.T) {
|
||
t.Title("Неуспешное создание заказ из-за отсутствия информации о стоках товара")
|
||
|
||
const (
|
||
userID = 42
|
||
sku = 404
|
||
)
|
||
|
||
ctx := context.Background()
|
||
|
||
t.WithNewStep("Создание заказа", func(sCtx provider.StepCtx) {
|
||
req := &pb.OrderCreateRequest{
|
||
UserId: userID,
|
||
Items: []*pb.OrderItem{
|
||
{
|
||
Sku: sku,
|
||
Count: 1,
|
||
},
|
||
},
|
||
}
|
||
_, err := s.lomsClient.OrderCreate(ctx, req)
|
||
|
||
e, ok := status.FromError(err)
|
||
sCtx.Require().True(ok)
|
||
sCtx.Require().Equal(codes.FailedPrecondition, e.Code(), "expect 400 (failed precondition) status code, got: %s", err.Error())
|
||
})
|
||
}
|