All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 1m36s
278 lines
7.8 KiB
Go
278 lines
7.8 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/test/bufconn"
|
|
|
|
"git.techease.ru/Smart-search/smart-search-back/internal/database"
|
|
grpchandlers "git.techease.ru/Smart-search/smart-search-back/internal/grpc"
|
|
"git.techease.ru/Smart-search/smart-search-back/pkg/crypto"
|
|
authpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/auth"
|
|
invitepb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/invite"
|
|
requestpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/request"
|
|
supplierpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/supplier"
|
|
userpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/user"
|
|
)
|
|
|
|
const (
|
|
testJWTSecret = "test-jwt-secret-key-for-integration-tests"
|
|
testCryptoSecret = "test-crypto-secret-key-for-integration"
|
|
bufSize = 1024 * 1024
|
|
)
|
|
|
|
type IntegrationSuite struct {
|
|
suite.Suite
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
pgContainer *postgres.PostgresContainer
|
|
pool *pgxpool.Pool
|
|
grpcServer *grpc.Server
|
|
listener *bufconn.Listener
|
|
authClient authpb.AuthServiceClient
|
|
userClient userpb.UserServiceClient
|
|
inviteClient invitepb.InviteServiceClient
|
|
requestClient requestpb.RequestServiceClient
|
|
supplierClient supplierpb.SupplierServiceClient
|
|
testUserEmail string
|
|
testUserPassword string
|
|
testAccessToken string
|
|
testRefreshToken string
|
|
}
|
|
|
|
func TestIntegrationSuite(t *testing.T) {
|
|
suite.Run(t, new(IntegrationSuite))
|
|
}
|
|
|
|
func (s *IntegrationSuite) SetupSuite() {
|
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
|
|
|
s.T().Log("Starting PostgreSQL container...")
|
|
pgContainer, err := postgres.Run(s.ctx,
|
|
"postgres:15-alpine",
|
|
postgres.WithDatabase("test_db"),
|
|
postgres.WithUsername("test_user"),
|
|
postgres.WithPassword("test_password"),
|
|
testcontainers.WithWaitStrategy(
|
|
wait.ForLog("database system is ready to accept connections").
|
|
WithOccurrence(2).
|
|
WithStartupTimeout(60*time.Second)),
|
|
)
|
|
s.Require().NoError(err)
|
|
s.pgContainer = pgContainer
|
|
|
|
connStr, err := pgContainer.ConnectionString(s.ctx, "sslmode=disable")
|
|
s.Require().NoError(err)
|
|
s.T().Logf("PostgreSQL connection string: %s", connStr)
|
|
|
|
s.T().Log("Running migrations...")
|
|
err = database.RunMigrationsFromPath(connStr, "../migrations")
|
|
s.Require().NoError(err)
|
|
|
|
s.T().Log("Creating connection pool...")
|
|
poolConfig, err := pgxpool.ParseConfig(connStr)
|
|
s.Require().NoError(err)
|
|
poolConfig.MaxConns = 10
|
|
|
|
pool, err := pgxpool.NewWithConfig(s.ctx, poolConfig)
|
|
s.Require().NoError(err)
|
|
s.pool = pool
|
|
|
|
err = pool.Ping(s.ctx)
|
|
s.Require().NoError(err)
|
|
|
|
s.T().Log("Creating gRPC server...")
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
authHandler, userHandler, inviteHandler, requestHandler, supplierHandler := grpchandlers.NewHandlers(
|
|
pool,
|
|
testJWTSecret,
|
|
testCryptoSecret,
|
|
"",
|
|
"",
|
|
logger,
|
|
)
|
|
|
|
s.listener = bufconn.Listen(bufSize)
|
|
s.grpcServer = grpc.NewServer()
|
|
|
|
grpchandlers.RegisterServices(s.grpcServer, authHandler, userHandler, inviteHandler, requestHandler, supplierHandler)
|
|
|
|
go func() {
|
|
if err := s.grpcServer.Serve(s.listener); err != nil {
|
|
s.T().Logf("gRPC server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
s.T().Log("Creating gRPC clients...")
|
|
conn, err := grpc.NewClient("passthrough://bufnet",
|
|
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
|
return s.listener.Dial()
|
|
}),
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
s.authClient = authpb.NewAuthServiceClient(conn)
|
|
s.userClient = userpb.NewUserServiceClient(conn)
|
|
s.inviteClient = invitepb.NewInviteServiceClient(conn)
|
|
s.requestClient = requestpb.NewRequestServiceClient(conn)
|
|
s.supplierClient = supplierpb.NewSupplierServiceClient(conn)
|
|
|
|
s.testUserEmail = fmt.Sprintf("test_%d@example.com", time.Now().Unix())
|
|
s.testUserPassword = "testpassword123"
|
|
|
|
s.T().Log("Creating test user...")
|
|
s.createTestUser("test@example.com", "testpassword")
|
|
|
|
s.T().Log("Integration suite setup completed")
|
|
}
|
|
|
|
func (s *IntegrationSuite) createTestUser(email, password string) {
|
|
cryptoHelper := crypto.NewCrypto(testCryptoSecret)
|
|
|
|
encryptedEmail, err := cryptoHelper.Encrypt(email)
|
|
s.Require().NoError(err)
|
|
|
|
encryptedPhone, err := cryptoHelper.Encrypt("+1234567890")
|
|
s.Require().NoError(err)
|
|
|
|
encryptedUserName, err := cryptoHelper.Encrypt("Test 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 NOTHING
|
|
`
|
|
|
|
_, err = s.pool.Exec(s.ctx, query,
|
|
encryptedEmail,
|
|
emailHash,
|
|
passwordHash,
|
|
encryptedPhone,
|
|
encryptedUserName,
|
|
"Test Company",
|
|
1000.0,
|
|
"active",
|
|
0,
|
|
10,
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *IntegrationSuite) createActiveInviteCode(canBeUsedCount int) int64 {
|
|
var inviteCode int64
|
|
query := `
|
|
INSERT INTO invite_codes (user_id, code, can_be_used_count, is_active, expires_at)
|
|
VALUES (1, FLOOR(100000 + RANDOM() * 900000)::bigint, $1, true, NOW() + INTERVAL '30 days')
|
|
RETURNING code
|
|
`
|
|
err := s.pool.QueryRow(s.ctx, query, canBeUsedCount).Scan(&inviteCode)
|
|
s.Require().NoError(err)
|
|
return inviteCode
|
|
}
|
|
|
|
func (s *IntegrationSuite) createExpiredInviteCode() int64 {
|
|
var inviteCode int64
|
|
query := `
|
|
INSERT INTO invite_codes (user_id, code, can_be_used_count, is_active, expires_at)
|
|
VALUES (1, FLOOR(100000 + RANDOM() * 900000)::bigint, 5, true, NOW() - INTERVAL '1 day')
|
|
RETURNING code
|
|
`
|
|
err := s.pool.QueryRow(s.ctx, query).Scan(&inviteCode)
|
|
s.Require().NoError(err)
|
|
return inviteCode
|
|
}
|
|
|
|
func (s *IntegrationSuite) TearDownSuite() {
|
|
s.T().Log("Tearing down integration suite...")
|
|
|
|
if s.grpcServer != nil {
|
|
s.grpcServer.Stop()
|
|
}
|
|
|
|
if s.pool != nil {
|
|
s.pool.Close()
|
|
}
|
|
|
|
if s.pgContainer != nil {
|
|
if err := s.pgContainer.Terminate(s.ctx); err != nil {
|
|
s.T().Logf("Failed to terminate PostgreSQL container: %v", err)
|
|
}
|
|
}
|
|
|
|
if s.cancel != nil {
|
|
s.cancel()
|
|
}
|
|
|
|
s.T().Log("Integration suite teardown completed")
|
|
}
|
|
|
|
func (s *IntegrationSuite) TearDownTest() {
|
|
s.testAccessToken = ""
|
|
s.testRefreshToken = ""
|
|
|
|
_, _ = s.pool.Exec(s.ctx, "DELETE FROM sessions")
|
|
_, _ = s.pool.Exec(s.ctx, "DELETE FROM invite_codes")
|
|
_, _ = 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
|
|
}
|