diff --git a/Makefile b/Makefile index 3adf434..e2ad0ee 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,9 @@ run-all: .PHONY: .integration-test integration-test: - ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=cart go test -v -tags=$(INTEGRATION_TAG) ./cart/tests/integration - CGO_ENABLED=0 ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=loms go test -v -tags=$(INTEGRATION_TAG) ./loms/tests/integration + # ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=cart go test -v -tags=$(INTEGRATION_TAG) ./cart/tests/integration + # CGO_ENABLED=0 ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=loms go test -v -tags=$(INTEGRATION_TAG) ./loms/tests/integration + CGO_ENABLED=0 ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=comments go test -v -tags=$(INTEGRATION_TAG) ./comments/tests/integration bench: go test -bench=BenchmarkInMemoryRepository -benchmem ./cart/internal/domain/cart/repository -benchtime 3s diff --git a/comments/infra/repository/sqlc/repository.go b/comments/infra/repository/sqlc/repository.go index c7cc404..9687cc2 100644 --- a/comments/infra/repository/sqlc/repository.go +++ b/comments/infra/repository/sqlc/repository.go @@ -77,8 +77,6 @@ func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Commen r.idMx.Lock() id := r.curID*1000 + shardID - r.curID++ - r.idMx.Unlock() req := &InsertCommentParams{ UserID: comment.UserID, @@ -92,6 +90,9 @@ func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Commen return nil, err } + r.curID++ + r.idMx.Unlock() + return mapComment(c), nil } @@ -125,12 +126,17 @@ func (r *commentsRepo) ListCommentsByUser(ctx context.Context, userID int64) ([] return nil, err2 } + alreadyMerged := make(map[int64]struct{}, len(l1)+len(l2)) merged := make([]*entity.Comment, 0, len(l1)+len(l2)) for _, com := range l1 { merged = append(merged, mapComment(com)) + alreadyMerged[com.Sku] = struct{}{} } for _, com := range l2 { - merged = append(merged, mapComment(com)) + if _, ok := alreadyMerged[com.Sku]; !ok { + merged = append(merged, mapComment(com)) + alreadyMerged[com.Sku] = struct{}{} + } } return merged, nil diff --git a/comments/tests/integration/comments_integration_test.go b/comments/tests/integration/comments_integration_test.go new file mode 100644 index 0000000..ec8b6af --- /dev/null +++ b/comments/tests/integration/comments_integration_test.go @@ -0,0 +1,230 @@ +//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) + }) +}