//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" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" repo "route256/comments/infra/repository/sqlc" "route256/comments/internal/app/server" "route256/comments/internal/domain/service" pb "route256/pkg/api/comments/v1" ) const ( 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": "comments_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/comments_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 Suite struct { suite.Suite grpcSrv *grpc.Server grpcConn *grpc.ClientConn client pb.CommentsClient cleanup func() } func TestLomsIntegrationSuite(t *testing.T) { suite.RunSuite(t, new(Suite)) } func (s *Suite) 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 repo := repo.NewCommentsRepository(pool, pool) svc := service.NewCommentsService(repo, 1) commentsServer := server.NewServer(svc) lis, err := net.Listen("tcp", "127.0.0.1:0") sCtx.Require().NoError(err) s.grpcSrv = grpc.NewServer() pb.RegisterCommentsServer(s.grpcSrv, commentsServer) 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.client = pb.NewCommentsClient(conn) }) } func (s *Suite) AfterAll(t provider.T) { s.grpcSrv.Stop() _ = s.grpcConn.Close() s.cleanup() } func (s *Suite) TestCommentListByOneUser_Success(t provider.T) { t.Title("Успешное получение списка комментариев, которые оставил один юзер по SKU") ctx := context.Background() const ( userID = int64(40) sku = int64(40) commentText1 = "тестовый комментарий 1" commentText2 = "тестовый комментарий 2" ) var ( commentIDs []int64 commentID int64 ) t.WithNewParameters("userID", userID, "sku", sku) t.WithNewStep("Создание комментария 1", func(sCtx provider.StepCtx) { req := &pb.CreateCommentRequest{ UserId: userID, Sku: sku, Comment: commentText1, } resp, err := s.client.CommentAdd(ctx, req) sCtx.Require().NoError(err) commentID = resp.Id commentIDs = append(commentIDs, commentID) }) t.WithNewParameters("commentID1", commentID) t.WithNewStep("Получение первого созданного комментария", func(sCtx provider.StepCtx) { req := &pb.ListByUserRequest{ UserId: userID, } resp, err := s.client.CommentListByUser(ctx, req) sCtx.Require().NoError(err) sCtx.Require().Len(resp.Comments, 1) sCtx.Require().NotNil(resp.Comments[0]) resultComment := resp.Comments[0] sCtx.Require().Equal(commentID, resultComment.Id) sCtx.Require().Equal(sku, resultComment.Sku) sCtx.Require().Equal(commentText1, resultComment.Text) }) t.WithNewStep("Создание комментария 2", func(sCtx provider.StepCtx) { req := &pb.CreateCommentRequest{ UserId: userID, Sku: sku, Comment: commentText2, } resp, err := s.client.CommentAdd(ctx, req) sCtx.Require().NoError(err) commentID = resp.Id commentIDs = append(commentIDs, commentID) }) t.WithNewParameters("commentID2", commentID) t.WithNewStep("Получение созданных комментариев в обратном хронологическом порядке", func(sCtx provider.StepCtx) { req := &pb.ListByUserRequest{ UserId: userID, } resp, err := s.client.CommentListByUser(ctx, req) sCtx.Require().NoError(err) sCtx.Require().Len(resp.Comments, 2) sCtx.Require().NotNil(resp.Comments[0]) sCtx.Require().NotNil(resp.Comments[1]) // commentIDs содержит в себе id комментариев в прямом хронологическом порядке sCtx.Require().Equal(commentIDs[1], resp.Comments[0].Id) sCtx.Require().Equal(sku, resp.Comments[0].Sku) sCtx.Require().Equal(commentText2, resp.Comments[0].Text) sCtx.Require().Equal(commentIDs[0], resp.Comments[1].Id) sCtx.Require().Equal(sku, resp.Comments[1].Sku) sCtx.Require().Equal(commentText1, resp.Comments[1].Text) }) }