add service
Some checks failed
Deploy Smart Search Backend Test / deploy (push) Has been cancelled

This commit is contained in:
vallyenfail
2026-01-19 16:24:33 +03:00
parent 5d52dc4ae2
commit 6d238a96ec
19 changed files with 717 additions and 21 deletions

View File

@@ -81,7 +81,7 @@ func main() {
boot.Bootstrap(ctx) 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) boot.WaitForShutdownSig(ctx)

View File

@@ -16,9 +16,5 @@ security:
jwt_secret: ${JWT_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=} jwt_secret: ${JWT_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=}
crypto_secret: ${CRYPTO_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=} crypto_secret: ${CRYPTO_SECRET:xM8KhJVkk28cIJeBo0306O2e6Ifni6tNVlcCMxDFAEc=}
grpc:
port: ${GRPC_PORT:9091}
max_connections: ${GRPC_MAX_CONNS:100}
logging: logging:
level: ${LOG_LEVEL:info} level: ${LOG_LEVEL:info}

View File

@@ -12,7 +12,6 @@ type Config struct {
Database DatabaseConfig `yaml:"database"` Database DatabaseConfig `yaml:"database"`
AI AIConfig `yaml:"ai"` AI AIConfig `yaml:"ai"`
Security SecurityConfig `yaml:"security"` Security SecurityConfig `yaml:"security"`
GRPC GRPCConfig `yaml:"grpc"`
Logging LoggingConfig `yaml:"logging"` Logging LoggingConfig `yaml:"logging"`
} }
@@ -37,11 +36,6 @@ type SecurityConfig struct {
CryptoSecret string `yaml:"crypto_secret"` CryptoSecret string `yaml:"crypto_secret"`
} }
type GRPCConfig struct {
Port int `yaml:"port"`
MaxConnections int `yaml:"max_connections"`
}
type LoggingConfig struct { type LoggingConfig struct {
Level string `yaml:"level"` Level string `yaml:"level"`
} }

View File

@@ -73,7 +73,7 @@ func (h *RequestHandler) GetMailingListByID(ctx context.Context, req *pb.GetMail
return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID") 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 { if err != nil {
return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID") return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID")
} }

View File

@@ -62,7 +62,7 @@ func NewHandlers(pool *pgxpool.Pool, jwtSecret, cryptoSecret, openAIKey, perplex
userService := service.NewUserService(userRepo, requestRepo, cryptoSecret) userService := service.NewUserService(userRepo, requestRepo, cryptoSecret)
inviteService := service.NewInviteService(inviteRepo, userRepo, txManager) inviteService := service.NewInviteService(inviteRepo, userRepo, txManager)
requestService := service.NewRequestService(requestRepo, supplierRepo, tokenUsageRepo, userRepo, openAIClient, perplexityClient, 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}, return &AuthHandler{authService: authService, logger: logger},
&UserHandler{userService: userService, logger: logger}, &UserHandler{userService: userService, logger: logger},

View File

@@ -14,7 +14,7 @@ func (h *SupplierHandler) ExportExcel(ctx context.Context, req *pb.ExportExcelRe
return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel") 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 { if err != nil {
return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel") return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel")
} }

View File

@@ -52,6 +52,7 @@ type RequestRepository interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Request, error) GetByID(ctx context.Context, id uuid.UUID) (*model.Request, error)
GetDetailByID(ctx context.Context, id uuid.UUID) (*model.RequestDetail, error) GetDetailByID(ctx context.Context, id uuid.UUID) (*model.RequestDetail, error)
GetUserStatistics(ctx context.Context, userID int) (requestsCount, suppliersCount, createdTZ int, err 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 { type SupplierRepository interface {

View File

@@ -214,3 +214,24 @@ func (r *requestRepository) GetUserStatistics(ctx context.Context, userID int) (
return requestsCount, suppliersCount, createdTZ, nil 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
}

View File

@@ -31,9 +31,9 @@ type RequestService interface {
CreateTZ(ctx context.Context, userID int, requestTxt string) (uuid.UUID, string, error) 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) ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error)
GetMailingList(ctx context.Context, userID int) ([]*model.Request, 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 { type SupplierService interface {
ExportExcel(ctx context.Context, requestID uuid.UUID) ([]byte, error) ExportExcel(ctx context.Context, requestID uuid.UUID, userID int) ([]byte, error)
} }

View File

@@ -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) { 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 return nil, err
} }
var suppliers []*model.Supplier var suppliers []*model.Supplier
var promptTokens, responseTokens int var promptTokens, responseTokens int
var err error
for attempt := 0; attempt < 3; attempt++ { for attempt := 0; attempt < 3; attempt++ {
suppliers, promptTokens, responseTokens, err = s.perplexity.FindSuppliers(tzText) 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) 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) return s.requestRepo.GetDetailByID(ctx, requestID)
} }

View File

@@ -5,21 +5,32 @@ import (
"fmt" "fmt"
"git.techease.ru/Smart-search/smart-search-back/internal/repository" "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/google/uuid"
"github.com/xuri/excelize/v2" "github.com/xuri/excelize/v2"
) )
type supplierService struct { type supplierService struct {
supplierRepo repository.SupplierRepository supplierRepo repository.SupplierRepository
requestRepo repository.RequestRepository
} }
func NewSupplierService(supplierRepo repository.SupplierRepository) SupplierService { func NewSupplierService(supplierRepo repository.SupplierRepository, requestRepo repository.RequestRepository) SupplierService {
return &supplierService{ return &supplierService{
supplierRepo: supplierRepo, 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) suppliers, err := s.supplierRepo.GetByRequestID(ctx, requestID)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -11,6 +11,7 @@ const (
InsufficientBalance = "INSUFFICIENT_BALANCE" InsufficientBalance = "INSUFFICIENT_BALANCE"
UserNotFound = "USER_NOT_FOUND" UserNotFound = "USER_NOT_FOUND"
RequestNotFound = "REQUEST_NOT_FOUND" RequestNotFound = "REQUEST_NOT_FOUND"
PermissionDenied = "PERMISSION_DENIED"
DatabaseError = "DATABASE_ERROR" DatabaseError = "DATABASE_ERROR"
EncryptionError = "ENCRYPTION_ERROR" EncryptionError = "ENCRYPTION_ERROR"

View File

@@ -91,6 +91,8 @@ func ToGRPCError(err error, zapLogger *zap.Logger, method string) error {
switch appErr.Code { switch appErr.Code {
case AuthInvalidCredentials, AuthMissing, AuthInvalidToken, RefreshInvalid: case AuthInvalidCredentials, AuthMissing, AuthInvalidToken, RefreshInvalid:
return status.Error(codes.Unauthenticated, appErr.Message) return status.Error(codes.Unauthenticated, appErr.Message)
case PermissionDenied:
return status.Error(codes.PermissionDenied, appErr.Message)
case InviteLimitReached: case InviteLimitReached:
return status.Error(codes.ResourceExhausted, appErr.Message) return status.Error(codes.ResourceExhausted, appErr.Message)
case InsufficientBalance, InviteInvalidOrExpired: case InsufficientBalance, InviteInvalidOrExpired:

View File

@@ -283,3 +283,140 @@ func (s *IntegrationSuite) TestEdgeCase_LoginWithWrongPassword() {
s.True(ok) s.True(ok)
s.Equal(codes.Unauthenticated, st.Code()) 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)
}

View File

@@ -184,6 +184,83 @@ func (s *IntegrationSuite) TestFullFlow_InviteCodeLifecycle() {
s.True(logoutResp.Success) 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() { func (s *IntegrationSuite) TestFullFlow_MultipleRefresh() {
ctx := context.Background() ctx := context.Background()

View File

@@ -233,3 +233,45 @@ func (s *IntegrationSuite) TearDownTest() {
_, _ = s.pool.Exec(s.ctx, "DELETE FROM suppliers") _, _ = s.pool.Exec(s.ctx, "DELETE FROM suppliers")
_, _ = s.pool.Exec(s.ctx, "DELETE FROM requests_for_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
}

214
tests/ownership_test.go Normal file
View File

@@ -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)
}

View File

@@ -136,3 +136,133 @@ func (s *IntegrationSuite) TestRequestHandler_GetMailingListWithValidUser() {
s.NoError(err) s.NoError(err)
s.NotNil(resp) 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))
}

View File

@@ -93,3 +93,58 @@ func (s *IntegrationSuite) TestSupplierHandler_ExportExcelWithValidRequest() {
s.NotEmpty(exportResp.FileName) s.NotEmpty(exportResp.FileName)
s.Equal("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", exportResp.MimeType) 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)
}