//go:build integration // +build integration package integration import ( "context" "encoding/json" "fmt" "net" "net/http" "net/http/httptest" "testing" "time" "github.com/ozontech/allure-go/pkg/framework/provider" "github.com/ozontech/allure-go/pkg/framework/suite" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "google.golang.org/grpc" lomsService "route256/cart/internal/adapter/client/loms" productsService "route256/cart/internal/adapter/client/products" "route256/cart/internal/app" "route256/cart/internal/domain/entity" "route256/cart/internal/domain/model" "route256/cart/internal/domain/repository" cartService "route256/cart/internal/domain/service" pbLoms "route256/pkg/api/loms/v1" ) const ( productServiceImage = "gitlab-registry.ozon.dev/go/classroom-18/students/base/products:latest" testToken = "testToken" testSku = 1076963 testName = "Теория нравственных чувств | Смит Адам" testUID = entity.UID(1337) ) type lomsServerMock struct { pbLoms.UnimplementedLOMSServer } func (lomsServerMock) OrderCreate(_ context.Context, _ *pbLoms.OrderCreateRequest) (*pbLoms.OrderCreateResponse, error) { return &pbLoms.OrderCreateResponse{OrderId: 1}, nil } func (lomsServerMock) StocksInfo(_ context.Context, _ *pbLoms.StocksInfoRequest) (*pbLoms.StocksInfoResponse, error) { return &pbLoms.StocksInfoResponse{Count: 1000}, nil } type CartHandlerSuite struct { suite.Suite psContainer testcontainers.Container lomsSrv *grpc.Server lomsConn *grpc.ClientConn server *httptest.Server cartSvc *cartService.CartService } func TestCartHandlerSuite(t *testing.T) { suite.RunSuite(t, new(CartHandlerSuite)) } func (s *CartHandlerSuite) BeforeAll(t provider.T) { ctx := context.Background() t.WithNewStep("start product-service container", func(sCtx provider.StepCtx) { req := testcontainers.ContainerRequest{ Image: productServiceImage, ExposedPorts: []string{"8082/tcp"}, WaitingFor: wait.ForHTTP("/docs").WithStartupTimeout(10 * time.Second), } container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) sCtx.Require().NoError(err, "create container") s.psContainer = container }) var productURL string t.WithNewStep("discover product-service URL", func(sCtx provider.StepCtx) { endpoint, err := s.psContainer.Endpoint(ctx, "") sCtx.Require().NoError(err) productURL = endpoint }) t.WithNewStep("start loms grpc server", func(st provider.StepCtx) { lis, err := net.Listen("tcp", "127.0.0.1:0") st.Require().NoError(err) s.lomsSrv = grpc.NewServer() pbLoms.RegisterLOMSServer(s.lomsSrv, &lomsServerMock{}) go s.lomsSrv.Serve(lis) conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure(), grpc.WithBlock()) st.Require().NoError(err) s.lomsConn = conn }) t.WithNewStep("init cart-service", func(sCtx provider.StepCtx) { prodClient := productsService.NewProductService( *http.DefaultClient, testToken, productURL, ) lomsClient := pbLoms.NewLOMSClient(s.lomsConn) repo := repository.NewInMemoryRepository(10) s.cartSvc = cartService.NewCartService(repo, prodClient, lomsService.NewLomsService(lomsClient)) appSrv := &app.App{} s.server = httptest.NewServer(appSrv.BootstrapHandlers(s.cartSvc)) }) } func (s *CartHandlerSuite) AfterAll(t provider.T) { s.server.Close() s.lomsSrv.Stop() s.lomsConn.Close() _ = s.psContainer.Terminate(context.Background()) } // DELETE /user//cart/ func (s *CartHandlerSuite) TestDeleteItemPositive(t provider.T) { ctx := context.Background() item := &model.Item{ Product: &model.Product{ Sku: testSku, }, Count: 1, } t.WithNewStep("fill cart in usecase", func(sCtx provider.StepCtx) { err := s.cartSvc.AddItem(context.Background(), testUID, item) sCtx.Require().NoError(err) cart, err := s.cartSvc.GetItemsByUserID(ctx, testUID) sCtx.Require().NoError(err) sCtx.Require().Len(cart.Items, 1, "check cart contains product") sCtx.Require().Equal(testUID, cart.UserID, "check user ID equals") sCtx.Require().Equal(item.Product.Sku, cart.Items[0].Product.Sku, "check product sku equals") }) t.WithNewStep("call DELETE handler", func(sCtx provider.StepCtx) { url := fmt.Sprintf("%s/user/%d/cart/%d", s.server.URL, testUID, testSku) req, err := http.NewRequest(http.MethodDelete, url, nil) sCtx.Require().NoError(err) resp, err := s.server.Client().Do(req) sCtx.Require().NoError(err) defer resp.Body.Close() sCtx.Require().Equal(http.StatusNoContent, resp.StatusCode, "delete handler unexpected status code") }) t.WithNewStep("check cart empty in usecase", func(sCtx provider.StepCtx) { _, err := s.cartSvc.GetItemsByUserID(ctx, testUID) sCtx.Require().Error(err) }) } // GET /user//cart func (s *CartHandlerSuite) TestGetCartPositive(t provider.T) { ctx := context.Background() item := &model.Item{ Product: &model.Product{ Sku: testSku, }, Count: 2, } t.WithNewStep("fill cart in usecase", func(sCtx provider.StepCtx) { err := s.cartSvc.AddItem(context.Background(), testUID, item) sCtx.Require().NoError(err) cart, err := s.cartSvc.GetItemsByUserID(ctx, testUID) sCtx.Require().NoError(err) sCtx.Require().Len(cart.Items, 1, "check cart contains product") sCtx.Require().Equal(testUID, cart.UserID, "check user ID equals") sCtx.Require().Equal(item.Product.Sku, cart.Items[0].Product.Sku, "check product sku equals") sCtx.Require().Equal(item.Count, cart.Items[0].Count, "check product count") }) t.WithNewStep("call GET handler", func(sCtx provider.StepCtx) { url := fmt.Sprintf("%s/user/%d/cart", s.server.URL, testUID) req, err := http.NewRequest(http.MethodGet, url, nil) sCtx.Require().NoError(err) resp, err := s.server.Client().Do(req) sCtx.Require().NoError(err) defer resp.Body.Close() sCtx.Require().Equal(http.StatusOK, resp.StatusCode, "check GET status code") sCtx.Require().Equal("application/json", resp.Header.Get("Content-Type"), "check GET content type") var body struct { Items []struct { Sku int64 `json:"sku"` Name string `json:"name"` Count uint32 `json:"count"` Price uint32 `json:"price"` } `json:"items"` TotalPrice uint32 `json:"total_price"` } sCtx.Require().NoError(json.NewDecoder(resp.Body).Decode(&body)) sCtx.Require().Len(body.Items, 1, "check items len") sCtx.Require().Equal(int64(testSku), body.Items[0].Sku, "check sku") sCtx.Require().Equal(uint32(2), body.Items[0].Count, "check item count") sCtx.Require().Greater(body.TotalPrice, uint32(0), "check price") }) }