diff --git a/cmd/server/main.go b/cmd/server/main.go index 90d88a0..2d607b1 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -81,7 +81,7 @@ func main() { boot.Bootstrap(ctx) - log.Println("gRPC server started via rk-boot on port 9091") + log.Println("gRPC server started via rk-boot") boot.WaitForShutdownSig(ctx) diff --git a/config/config.yaml.example b/config/config.yaml.example index d130da5..37c149f 100644 --- a/config/config.yaml.example +++ b/config/config.yaml.example @@ -16,9 +16,5 @@ security: jwt_secret: ${JWT_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=} crypto_secret: ${CRYPTO_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=} -grpc: - port: ${GRPC_PORT:9091} - max_connections: ${GRPC_MAX_CONNS:100} - logging: level: ${LOG_LEVEL:info} diff --git a/internal/config/config.go b/internal/config/config.go index 9f9b061..ab97a3c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,7 +12,6 @@ type Config struct { Database DatabaseConfig `yaml:"database"` AI AIConfig `yaml:"ai"` Security SecurityConfig `yaml:"security"` - GRPC GRPCConfig `yaml:"grpc"` Logging LoggingConfig `yaml:"logging"` } @@ -37,11 +36,6 @@ type SecurityConfig struct { CryptoSecret string `yaml:"crypto_secret"` } -type GRPCConfig struct { - Port int `yaml:"port"` - MaxConnections int `yaml:"max_connections"` -} - type LoggingConfig struct { Level string `yaml:"level"` } diff --git a/internal/grpc/request_handler.go b/internal/grpc/request_handler.go index 352989c..1585b9c 100644 --- a/internal/grpc/request_handler.go +++ b/internal/grpc/request_handler.go @@ -73,7 +73,7 @@ func (h *RequestHandler) GetMailingListByID(ctx context.Context, req *pb.GetMail return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID") } - detail, err := h.requestService.GetMailingListByID(ctx, requestID) + detail, err := h.requestService.GetMailingListByID(ctx, requestID, int(req.UserId)) if err != nil { return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID") } diff --git a/internal/grpc/server.go b/internal/grpc/server.go index 5333000..3475cf9 100644 --- a/internal/grpc/server.go +++ b/internal/grpc/server.go @@ -62,7 +62,7 @@ func NewHandlers(pool *pgxpool.Pool, jwtSecret, cryptoSecret, openAIKey, perplex userService := service.NewUserService(userRepo, requestRepo, cryptoSecret) inviteService := service.NewInviteService(inviteRepo, userRepo, txManager) requestService := service.NewRequestService(requestRepo, supplierRepo, tokenUsageRepo, userRepo, openAIClient, perplexityClient, txManager) - supplierService := service.NewSupplierService(supplierRepo) + supplierService := service.NewSupplierService(supplierRepo, requestRepo) return &AuthHandler{authService: authService, logger: logger}, &UserHandler{userService: userService, logger: logger}, diff --git a/internal/grpc/supplier_handler.go b/internal/grpc/supplier_handler.go index 83b8652..8f6be2b 100644 --- a/internal/grpc/supplier_handler.go +++ b/internal/grpc/supplier_handler.go @@ -14,7 +14,7 @@ func (h *SupplierHandler) ExportExcel(ctx context.Context, req *pb.ExportExcelRe return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel") } - fileData, err := h.supplierService.ExportExcel(ctx, requestID) + fileData, err := h.supplierService.ExportExcel(ctx, requestID, int(req.UserId)) if err != nil { return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel") } diff --git a/internal/repository/interfaces.go b/internal/repository/interfaces.go index c4a98ad..6392b40 100644 --- a/internal/repository/interfaces.go +++ b/internal/repository/interfaces.go @@ -52,6 +52,7 @@ type RequestRepository interface { GetByID(ctx context.Context, id uuid.UUID) (*model.Request, error) GetDetailByID(ctx context.Context, id uuid.UUID) (*model.RequestDetail, error) GetUserStatistics(ctx context.Context, userID int) (requestsCount, suppliersCount, createdTZ int, err error) + CheckOwnership(ctx context.Context, requestID uuid.UUID, userID int) (bool, error) } type SupplierRepository interface { diff --git a/internal/repository/request.go b/internal/repository/request.go index d31215e..3ccd7e0 100644 --- a/internal/repository/request.go +++ b/internal/repository/request.go @@ -214,3 +214,24 @@ func (r *requestRepository) GetUserStatistics(ctx context.Context, userID int) ( return requestsCount, suppliersCount, createdTZ, nil } + +func (r *requestRepository) CheckOwnership(ctx context.Context, requestID uuid.UUID, userID int) (bool, error) { + query := r.qb.Select("1").From("requests_for_suppliers"). + Where(sq.Eq{"id": requestID, "user_id": userID}) + + sqlQuery, args, err := query.ToSql() + if err != nil { + return false, errs.NewInternalError(errs.DatabaseError, "failed to build query", err) + } + + var exists int + err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&exists) + if errors.Is(err, pgx.ErrNoRows) { + return false, nil + } + if err != nil { + return false, errs.NewInternalError(errs.DatabaseError, "failed to check ownership", err) + } + + return true, nil +} diff --git a/internal/service/interfaces.go b/internal/service/interfaces.go index ffa09fa..e581dee 100644 --- a/internal/service/interfaces.go +++ b/internal/service/interfaces.go @@ -31,9 +31,9 @@ type RequestService interface { CreateTZ(ctx context.Context, userID int, requestTxt string) (uuid.UUID, string, error) ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error) GetMailingList(ctx context.Context, userID int) ([]*model.Request, error) - GetMailingListByID(ctx context.Context, requestID uuid.UUID) (*model.RequestDetail, error) + GetMailingListByID(ctx context.Context, requestID uuid.UUID, userID int) (*model.RequestDetail, error) } type SupplierService interface { - ExportExcel(ctx context.Context, requestID uuid.UUID) ([]byte, error) + ExportExcel(ctx context.Context, requestID uuid.UUID, userID int) ([]byte, error) } diff --git a/internal/service/request.go b/internal/service/request.go index 41483e8..6c2af6c 100644 --- a/internal/service/request.go +++ b/internal/service/request.go @@ -107,13 +107,20 @@ func (s *requestService) CreateTZ(ctx context.Context, userID int, requestTxt st } func (s *requestService) ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error) { - if err := s.requestRepo.UpdateFinalTZ(ctx, requestID, tzText); err != nil { + isOwner, err := s.requestRepo.CheckOwnership(ctx, requestID, userID) + if err != nil { + return nil, err + } + if !isOwner { + return nil, errors.NewBusinessError(errors.PermissionDenied, "access denied to this request") + } + + if err = s.requestRepo.UpdateFinalTZ(ctx, requestID, tzText); err != nil { return nil, err } var suppliers []*model.Supplier var promptTokens, responseTokens int - var err error for attempt := 0; attempt < 3; attempt++ { suppliers, promptTokens, responseTokens, err = s.perplexity.FindSuppliers(tzText) @@ -169,6 +176,14 @@ func (s *requestService) GetMailingList(ctx context.Context, userID int) ([]*mod return s.requestRepo.GetByUserID(ctx, userID) } -func (s *requestService) GetMailingListByID(ctx context.Context, requestID uuid.UUID) (*model.RequestDetail, error) { +func (s *requestService) GetMailingListByID(ctx context.Context, requestID uuid.UUID, userID int) (*model.RequestDetail, error) { + isOwner, err := s.requestRepo.CheckOwnership(ctx, requestID, userID) + if err != nil { + return nil, err + } + if !isOwner { + return nil, errors.NewBusinessError(errors.PermissionDenied, "access denied to this request") + } + return s.requestRepo.GetDetailByID(ctx, requestID) } diff --git a/internal/service/supplier.go b/internal/service/supplier.go index dbdf441..8fc506e 100644 --- a/internal/service/supplier.go +++ b/internal/service/supplier.go @@ -5,21 +5,32 @@ import ( "fmt" "git.techease.ru/Smart-search/smart-search-back/internal/repository" + "git.techease.ru/Smart-search/smart-search-back/pkg/errors" "github.com/google/uuid" "github.com/xuri/excelize/v2" ) type supplierService struct { supplierRepo repository.SupplierRepository + requestRepo repository.RequestRepository } -func NewSupplierService(supplierRepo repository.SupplierRepository) SupplierService { +func NewSupplierService(supplierRepo repository.SupplierRepository, requestRepo repository.RequestRepository) SupplierService { return &supplierService{ supplierRepo: supplierRepo, + requestRepo: requestRepo, } } -func (s *supplierService) ExportExcel(ctx context.Context, requestID uuid.UUID) ([]byte, error) { +func (s *supplierService) ExportExcel(ctx context.Context, requestID uuid.UUID, userID int) ([]byte, error) { + isOwner, err := s.requestRepo.CheckOwnership(ctx, requestID, userID) + if err != nil { + return nil, err + } + if !isOwner { + return nil, errors.NewBusinessError(errors.PermissionDenied, "access denied to this request") + } + suppliers, err := s.supplierRepo.GetByRequestID(ctx, requestID) if err != nil { return nil, err diff --git a/pkg/errors/codes.go b/pkg/errors/codes.go index 916bdbd..648c609 100644 --- a/pkg/errors/codes.go +++ b/pkg/errors/codes.go @@ -11,6 +11,7 @@ const ( InsufficientBalance = "INSUFFICIENT_BALANCE" UserNotFound = "USER_NOT_FOUND" RequestNotFound = "REQUEST_NOT_FOUND" + PermissionDenied = "PERMISSION_DENIED" DatabaseError = "DATABASE_ERROR" EncryptionError = "ENCRYPTION_ERROR" diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index b4f6ff3..fbd22d1 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -91,6 +91,8 @@ func ToGRPCError(err error, zapLogger *zap.Logger, method string) error { switch appErr.Code { case AuthInvalidCredentials, AuthMissing, AuthInvalidToken, RefreshInvalid: return status.Error(codes.Unauthenticated, appErr.Message) + case PermissionDenied: + return status.Error(codes.PermissionDenied, appErr.Message) case InviteLimitReached: return status.Error(codes.ResourceExhausted, appErr.Message) case InsufficientBalance, InviteInvalidOrExpired: diff --git a/tests/edge_cases_test.go b/tests/edge_cases_test.go index 42fe9a7..d12a05a 100644 --- a/tests/edge_cases_test.go +++ b/tests/edge_cases_test.go @@ -283,3 +283,140 @@ func (s *IntegrationSuite) TestEdgeCase_LoginWithWrongPassword() { s.True(ok) s.Equal(codes.Unauthenticated, st.Code()) } + +func (s *IntegrationSuite) TestEdgeCase_ApproveTZTwice() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Тест двойного approve", + } + + createResp, err := s.requestClient.CreateTZ(ctx, createReq) + s.NoError(err) + requestID := createResp.RequestId + + approveReq1 := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Первое утверждение", + UserId: userID, + } + + approveResp1, err := s.requestClient.ApproveTZ(ctx, approveReq1) + s.NoError(err) + s.True(approveResp1.Success) + + approveReq2 := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Второе утверждение", + UserId: userID, + } + + approveResp2, err := s.requestClient.ApproveTZ(ctx, approveReq2) + s.NoError(err) + s.True(approveResp2.Success) +} + +func (s *IntegrationSuite) TestEdgeCase_CreateTZWithVeryLongText() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + + _, err = s.pool.Exec(ctx, "UPDATE users SET balance = 10000 WHERE id = $1", validateResp.UserId) + s.NoError(err) + + longText := "Нужны поставщики. " + for i := 0; i < 500; i++ { + longText += "Дополнительные требования к качеству и срокам поставки материалов. " + } + + req := &requestpb.CreateTZRequest{ + UserId: validateResp.UserId, + RequestTxt: longText, + } + + resp, err := s.requestClient.CreateTZ(ctx, req) + s.NoError(err) + s.NotNil(resp) + s.NotEmpty(resp.RequestId) + s.NotEmpty(resp.TzText) +} + +func (s *IntegrationSuite) TestEdgeCase_ApproveTZWithVeryLongFinalTZ() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Тест длинного ТЗ", + } + + createResp, err := s.requestClient.CreateTZ(ctx, createReq) + s.NoError(err) + requestID := createResp.RequestId + + longFinalTZ := "ТЕХНИЧЕСКОЕ ЗАДАНИЕ\n\n" + for i := 0; i < 500; i++ { + longFinalTZ += "Пункт требований с детальным описанием спецификации и условий поставки. " + } + + approveReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: longFinalTZ, + UserId: userID, + } + + approveResp, err := s.requestClient.ApproveTZ(ctx, approveReq) + s.NoError(err) + s.True(approveResp.Success) +} diff --git a/tests/full_flow_test.go b/tests/full_flow_test.go index e71fe59..71cc87e 100644 --- a/tests/full_flow_test.go +++ b/tests/full_flow_test.go @@ -184,6 +184,83 @@ func (s *IntegrationSuite) TestFullFlow_InviteCodeLifecycle() { s.True(logoutResp.Success) } +func (s *IntegrationSuite) TestFullFlow_CreateTZ_ApproveTZ_GetMailingListByID_ExportExcel() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + s.NotEmpty(loginResp.AccessToken) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + s.True(validateResp.Valid) + userID := validateResp.UserId + + createTZReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Нужны поставщики офисной мебели: столы 20 шт, стулья 50 шт", + } + + createTZResp, err := s.requestClient.CreateTZ(ctx, createTZReq) + s.NoError(err) + s.NotEmpty(createTZResp.RequestId) + s.NotEmpty(createTZResp.TzText) + requestID := createTZResp.RequestId + + approveTZReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: createTZResp.TzText, + UserId: userID, + } + + approveTZResp, err := s.requestClient.ApproveTZ(ctx, approveTZReq) + s.NoError(err) + s.True(approveTZResp.Success) + + getMailingListByIDReq := &requestpb.GetMailingListByIDRequest{ + RequestId: requestID, + UserId: userID, + } + + mailingListByIDResp, err := s.requestClient.GetMailingListByID(ctx, getMailingListByIDReq) + s.NoError(err) + s.NotNil(mailingListByIDResp.Item) + s.Equal(requestID, mailingListByIDResp.Item.RequestId) + s.Greater(mailingListByIDResp.Item.SuppliersFound, int32(0)) + + exportExcelReq := &supplierpb.ExportExcelRequest{ + RequestId: requestID, + UserId: userID, + } + + exportExcelResp, err := s.supplierClient.ExportExcel(ctx, exportExcelReq) + s.NoError(err) + s.NotNil(exportExcelResp) + s.NotEmpty(exportExcelResp.FileName) + s.NotEmpty(exportExcelResp.FileData) + s.Equal("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", exportExcelResp.MimeType) + s.Greater(len(exportExcelResp.FileData), 0) + + logoutReq := &authpb.LogoutRequest{ + AccessToken: loginResp.AccessToken, + } + + logoutResp, err := s.authClient.Logout(ctx, logoutReq) + s.NoError(err) + s.True(logoutResp.Success) +} + func (s *IntegrationSuite) TestFullFlow_MultipleRefresh() { ctx := context.Background() diff --git a/tests/integration_suite_test.go b/tests/integration_suite_test.go index ae3d204..c0a7923 100644 --- a/tests/integration_suite_test.go +++ b/tests/integration_suite_test.go @@ -233,3 +233,45 @@ func (s *IntegrationSuite) TearDownTest() { _, _ = s.pool.Exec(s.ctx, "DELETE FROM suppliers") _, _ = s.pool.Exec(s.ctx, "DELETE FROM requests_for_suppliers") } + +func (s *IntegrationSuite) createSecondTestUser() (email string, password string, userID int64) { + email = "second_user@example.com" + password = "secondpassword" + + cryptoHelper := crypto.NewCrypto(testCryptoSecret) + + encryptedEmail, err := cryptoHelper.Encrypt(email) + s.Require().NoError(err) + + encryptedPhone, err := cryptoHelper.Encrypt("+9876543210") + s.Require().NoError(err) + + encryptedUserName, err := cryptoHelper.Encrypt("Second User") + s.Require().NoError(err) + + emailHash := cryptoHelper.EmailHash(email) + passwordHash := crypto.PasswordHash(password) + + query := ` + INSERT INTO users (email, email_hash, password_hash, phone, user_name, company_name, balance, payment_status, invites_issued, invites_limit) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT (email_hash) DO UPDATE SET balance = $7 + RETURNING id + ` + + err = s.pool.QueryRow(s.ctx, query, + encryptedEmail, + emailHash, + passwordHash, + encryptedPhone, + encryptedUserName, + "Second Company", + 1000.0, + "active", + 0, + 10, + ).Scan(&userID) + s.Require().NoError(err) + + return email, password, userID +} diff --git a/tests/ownership_test.go b/tests/ownership_test.go new file mode 100644 index 0000000..16e7a40 --- /dev/null +++ b/tests/ownership_test.go @@ -0,0 +1,214 @@ +package tests + +import ( + "context" + + authpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/auth" + requestpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/request" + supplierpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/supplier" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *IntegrationSuite) TestOwnership_GetMailingListByID_AnotherUsersRequest() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + user1ID := validateResp.UserId + + createTZReq := &requestpb.CreateTZRequest{ + UserId: user1ID, + RequestTxt: "Нужны поставщики для теста ownership", + } + + createTZResp, err := s.requestClient.CreateTZ(ctx, createTZReq) + s.NoError(err) + s.NotEmpty(createTZResp.RequestId) + requestID := createTZResp.RequestId + + _, _, user2ID := s.createSecondTestUser() + + getMailingByIDReq := &requestpb.GetMailingListByIDRequest{ + RequestId: requestID, + UserId: user2ID, + } + + resp, err := s.requestClient.GetMailingListByID(ctx, getMailingByIDReq) + s.Error(err) + s.Nil(resp) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) +} + +func (s *IntegrationSuite) TestOwnership_ApproveTZ_AnotherUsersRequest() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + user1ID := validateResp.UserId + + createTZReq := &requestpb.CreateTZRequest{ + UserId: user1ID, + RequestTxt: "Нужны поставщики для теста ownership approve", + } + + createTZResp, err := s.requestClient.CreateTZ(ctx, createTZReq) + s.NoError(err) + s.NotEmpty(createTZResp.RequestId) + requestID := createTZResp.RequestId + + _, _, user2ID := s.createSecondTestUser() + + approveTZReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Утвержденное ТЗ от чужого пользователя", + UserId: user2ID, + } + + resp, err := s.requestClient.ApproveTZ(ctx, approveTZReq) + s.Error(err) + s.Nil(resp) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) +} + +func (s *IntegrationSuite) TestOwnership_ExportExcel_AnotherUsersRequest() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + user1ID := validateResp.UserId + + createTZReq := &requestpb.CreateTZRequest{ + UserId: user1ID, + RequestTxt: "Нужны поставщики для теста ownership export", + } + + createTZResp, err := s.requestClient.CreateTZ(ctx, createTZReq) + s.NoError(err) + s.NotEmpty(createTZResp.RequestId) + requestID := createTZResp.RequestId + + approveTZReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Утвержденное ТЗ для экспорта", + UserId: user1ID, + } + + _, err = s.requestClient.ApproveTZ(ctx, approveTZReq) + s.NoError(err) + + _, _, user2ID := s.createSecondTestUser() + + exportReq := &supplierpb.ExportExcelRequest{ + RequestId: requestID, + UserId: user2ID, + } + + resp, err := s.supplierClient.ExportExcel(ctx, exportReq) + s.Error(err) + s.Nil(resp) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) +} + +func (s *IntegrationSuite) TestOwnership_GetMailingListByID_OwnRequest_Success() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createTZReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Нужны поставщики для теста ownership success", + } + + createTZResp, err := s.requestClient.CreateTZ(ctx, createTZReq) + s.NoError(err) + s.NotEmpty(createTZResp.RequestId) + requestID := createTZResp.RequestId + + approveTZReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Утвержденное ТЗ", + UserId: userID, + } + + _, err = s.requestClient.ApproveTZ(ctx, approveTZReq) + s.NoError(err) + + getMailingByIDReq := &requestpb.GetMailingListByIDRequest{ + RequestId: requestID, + UserId: userID, + } + + resp, err := s.requestClient.GetMailingListByID(ctx, getMailingByIDReq) + s.NoError(err) + s.NotNil(resp) + s.NotNil(resp.Item) + s.Equal(requestID, resp.Item.RequestId) +} diff --git a/tests/request_handler_test.go b/tests/request_handler_test.go index b9f9cd0..af2e508 100644 --- a/tests/request_handler_test.go +++ b/tests/request_handler_test.go @@ -136,3 +136,133 @@ func (s *IntegrationSuite) TestRequestHandler_GetMailingListWithValidUser() { s.NoError(err) s.NotNil(resp) } + +func (s *IntegrationSuite) TestRequestHandler_CreateTZWithFile() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + + req := &requestpb.CreateTZRequest{ + UserId: validateResp.UserId, + RequestTxt: "Нужны поставщики металлоконструкций", + FileData: []byte("Содержимое файла с дополнительными требованиями"), + FileName: "requirements.txt", + } + + resp, err := s.requestClient.CreateTZ(ctx, req) + s.NoError(err) + s.NotNil(resp) + s.NotEmpty(resp.RequestId) + s.NotEmpty(resp.TzText) +} + +func (s *IntegrationSuite) TestRequestHandler_ApproveTZSuccess() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Нужны поставщики кирпича для строительства", + } + + createResp, err := s.requestClient.CreateTZ(ctx, createReq) + s.NoError(err) + s.NotEmpty(createResp.RequestId) + + approveReq := &requestpb.ApproveTZRequest{ + RequestId: createResp.RequestId, + FinalTz: "Утвержденное техническое задание на поставку кирпича", + UserId: userID, + } + + approveResp, err := s.requestClient.ApproveTZ(ctx, approveReq) + s.NoError(err) + s.NotNil(approveResp) + s.True(approveResp.Success) + s.Equal("sent", approveResp.MailingStatus) +} + +func (s *IntegrationSuite) TestRequestHandler_GetMailingListByIDSuccess() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Нужны поставщики бетона", + } + + createResp, err := s.requestClient.CreateTZ(ctx, createReq) + s.NoError(err) + s.NotEmpty(createResp.RequestId) + requestID := createResp.RequestId + + approveReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Утвержденное ТЗ на поставку бетона", + UserId: userID, + } + + _, err = s.requestClient.ApproveTZ(ctx, approveReq) + s.NoError(err) + + getByIDReq := &requestpb.GetMailingListByIDRequest{ + RequestId: requestID, + UserId: userID, + } + + getByIDResp, err := s.requestClient.GetMailingListByID(ctx, getByIDReq) + s.NoError(err) + s.NotNil(getByIDResp) + s.NotNil(getByIDResp.Item) + s.Equal(requestID, getByIDResp.Item.RequestId) + s.Greater(getByIDResp.Item.SuppliersFound, int32(0)) +} diff --git a/tests/supplier_handler_test.go b/tests/supplier_handler_test.go index 0ff0ed8..9e07759 100644 --- a/tests/supplier_handler_test.go +++ b/tests/supplier_handler_test.go @@ -93,3 +93,58 @@ func (s *IntegrationSuite) TestSupplierHandler_ExportExcelWithValidRequest() { s.NotEmpty(exportResp.FileName) s.Equal("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", exportResp.MimeType) } + +func (s *IntegrationSuite) TestSupplierHandler_ExportExcelWithSuppliers() { + ctx := context.Background() + + loginReq := &authpb.LoginRequest{ + Email: "test@example.com", + Password: "testpassword", + Ip: "127.0.0.1", + UserAgent: "integration-test", + } + + loginResp, err := s.authClient.Login(ctx, loginReq) + s.NoError(err) + + validateReq := &authpb.ValidateRequest{ + AccessToken: loginResp.AccessToken, + } + + validateResp, err := s.authClient.Validate(ctx, validateReq) + s.NoError(err) + userID := validateResp.UserId + + createReq := &requestpb.CreateTZRequest{ + UserId: userID, + RequestTxt: "Нужны поставщики офисной мебели для большого офиса", + } + + createResp, err := s.requestClient.CreateTZ(ctx, createReq) + s.NoError(err) + s.NotEmpty(createResp.RequestId) + requestID := createResp.RequestId + + approveReq := &requestpb.ApproveTZRequest{ + RequestId: requestID, + FinalTz: "Техническое задание на поставку офисной мебели", + UserId: userID, + } + + approveResp, err := s.requestClient.ApproveTZ(ctx, approveReq) + s.NoError(err) + s.True(approveResp.Success) + + exportReq := &supplierpb.ExportExcelRequest{ + RequestId: requestID, + UserId: userID, + } + + exportResp, err := s.supplierClient.ExportExcel(ctx, exportReq) + s.NoError(err) + s.NotNil(exportResp) + s.NotEmpty(exportResp.FileName) + s.NotEmpty(exportResp.FileData) + s.Equal("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", exportResp.MimeType) + s.Greater(len(exportResp.FileData), 1000) +}