//go:build integration // +build integration package integration import ( "context" "database/sql" "fmt" "net" "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" "go.uber.org/goleak" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "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" ) 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) { defer goleak.VerifyNone(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) 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)) }) }