Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
356 lines
9.2 KiB
Go
356 lines
9.2 KiB
Go
package accountapiimp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/tech/sendico/pkg/db/repository/builder"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/server/interface/api/srequest"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Helper function to create string pointers
|
|
func stringPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
// TestTimezoneValidation tests timezone validation logic separately
|
|
func TestTimezoneValidation(t *testing.T) {
|
|
t.Run("ValidTimezones", func(t *testing.T) {
|
|
validTimezones := []string{
|
|
"UTC",
|
|
"America/New_York",
|
|
"Europe/London",
|
|
"Asia/Tokyo",
|
|
"Australia/Sydney",
|
|
}
|
|
|
|
for _, tz := range validTimezones {
|
|
t.Run(tz, func(t *testing.T) {
|
|
_, err := time.LoadLocation(tz)
|
|
assert.NoError(t, err, "Timezone %s should be valid", tz)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("InvalidTimezones", func(t *testing.T) {
|
|
invalidTimezones := []string{
|
|
"Invalid/Timezone",
|
|
"Not/A/Timezone",
|
|
"BadTimezone",
|
|
"America/NotACity",
|
|
}
|
|
|
|
for _, tz := range invalidTimezones {
|
|
t.Run(tz, func(t *testing.T) {
|
|
_, err := time.LoadLocation(tz)
|
|
assert.Error(t, err, "Timezone %s should be invalid", tz)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCreateValidSignupRequest tests the helper function for creating valid requests
|
|
func TestCreateValidSignupRequest(t *testing.T) {
|
|
request := srequest.Signup{
|
|
Account: model.AccountData{
|
|
LoginData: model.LoginData{
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "test@example.com",
|
|
},
|
|
Password: "TestPassword123!",
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
Organization: model.Describable{
|
|
Name: "Test Organization",
|
|
},
|
|
OrganizationTimeZone: "UTC",
|
|
OwnerRole: model.Describable{
|
|
Name: "Owner",
|
|
},
|
|
}
|
|
|
|
// Validate the request structure
|
|
assert.Equal(t, "test@example.com", request.Account.Login)
|
|
assert.Equal(t, "TestPassword123!", request.Account.Password)
|
|
assert.Equal(t, "Test User", request.Account.Name)
|
|
assert.Equal(t, "Test Organization", request.Organization.Name)
|
|
assert.Equal(t, "UTC", request.OrganizationTimeZone)
|
|
}
|
|
|
|
// TestSignupRequestValidation tests various signup request validation scenarios
|
|
func TestSignupRequestValidation(t *testing.T) {
|
|
t.Run("ValidRequest", func(t *testing.T) {
|
|
request := srequest.Signup{
|
|
Account: model.AccountData{
|
|
LoginData: model.LoginData{
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "test@example.com",
|
|
},
|
|
Password: "TestPassword123!",
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
Organization: model.Describable{
|
|
Name: "Test Organization",
|
|
},
|
|
OrganizationTimeZone: "UTC",
|
|
}
|
|
|
|
// Basic validation - all required fields present
|
|
assert.NotEmpty(t, request.Account.Login)
|
|
assert.NotEmpty(t, request.Account.Password)
|
|
assert.NotEmpty(t, request.Account.Name)
|
|
assert.NotEmpty(t, request.Organization.Name)
|
|
assert.NotEmpty(t, request.OrganizationTimeZone)
|
|
})
|
|
|
|
t.Run("EmailFormats", func(t *testing.T) {
|
|
validEmails := []string{
|
|
"test@example.com",
|
|
"user.name@example.com",
|
|
"user+tag@example.org",
|
|
"test123@domain.co.uk",
|
|
}
|
|
|
|
for _, email := range validEmails {
|
|
t.Run(email, func(t *testing.T) {
|
|
request := srequest.Signup{
|
|
Account: model.AccountData{
|
|
LoginData: model.LoginData{
|
|
UserDataBase: model.UserDataBase{
|
|
Login: email,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
assert.Equal(t, email, request.Account.Login)
|
|
assert.Contains(t, email, "@")
|
|
assert.Contains(t, email, ".")
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("PasswordComplexity", func(t *testing.T) {
|
|
passwordTests := []struct {
|
|
name string
|
|
password string
|
|
valid bool
|
|
}{
|
|
{"Strong", "TestPassword123!", true},
|
|
{"WithNumbers", "MyPass123!", true},
|
|
{"WithSymbols", "Complex@Pass1", true},
|
|
{"TooShort", "Test1!", false},
|
|
{"NoNumbers", "TestPassword!", false},
|
|
{"NoSymbols", "TestPassword123", false},
|
|
{"NoUppercase", "testpassword123!", false},
|
|
{"NoLowercase", "TESTPASSWORD123!", false},
|
|
}
|
|
|
|
for _, tt := range passwordTests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
request := srequest.Signup{
|
|
Account: model.AccountData{
|
|
LoginData: model.LoginData{
|
|
Password: tt.password,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Basic structure validation
|
|
assert.Equal(t, tt.password, request.Account.Password)
|
|
|
|
if tt.valid {
|
|
assert.True(t, len(tt.password) >= 8, "Password should be at least 8 characters")
|
|
} else {
|
|
// For invalid passwords, at least one condition should fail
|
|
hasDigit := false
|
|
hasUpper := false
|
|
hasLower := false
|
|
hasSpecial := false
|
|
|
|
for _, char := range tt.password {
|
|
switch {
|
|
case char >= '0' && char <= '9':
|
|
hasDigit = true
|
|
case char >= 'A' && char <= 'Z':
|
|
hasUpper = true
|
|
case char >= 'a' && char <= 'z':
|
|
hasLower = true
|
|
case char >= '!' && char <= '/' || char >= ':' && char <= '@':
|
|
hasSpecial = true
|
|
}
|
|
}
|
|
|
|
// At least one requirement should fail for invalid passwords
|
|
if len(tt.password) >= 8 {
|
|
assert.False(t, hasDigit && hasUpper && hasLower && hasSpecial,
|
|
"Password %s should fail at least one requirement", tt.password)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestAccountDataToAccount tests the ToAccount method
|
|
func TestAccountDataToAccount(t *testing.T) {
|
|
accountData := model.AccountData{
|
|
LoginData: model.LoginData{
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "test@example.com",
|
|
},
|
|
Password: "TestPassword123!",
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
}
|
|
|
|
account := accountData.ToAccount()
|
|
|
|
assert.Equal(t, accountData.Login, account.Login)
|
|
assert.Equal(t, accountData.Password, account.Password)
|
|
assert.Equal(t, accountData.Name, account.Name)
|
|
|
|
// Verify the account has proper structure
|
|
assert.NotNil(t, account)
|
|
assert.IsType(t, &model.Account{}, account)
|
|
}
|
|
|
|
// TestColorValidation tests that colors are properly formatted
|
|
func TestColorValidation(t *testing.T) {
|
|
validColors := []string{
|
|
"#FF0000", // Red
|
|
"#00FF00", // Green
|
|
"#0000FF", // Blue
|
|
"#FFFFFF", // White
|
|
"#000000", // Black
|
|
"#FF8000", // Orange
|
|
}
|
|
|
|
for _, color := range validColors {
|
|
t.Run(color, func(t *testing.T) {
|
|
colorPtr := stringPtr(color)
|
|
assert.NotNil(t, colorPtr)
|
|
assert.Equal(t, color, *colorPtr)
|
|
assert.True(t, len(color) == 7, "Color should be 7 characters long")
|
|
assert.True(t, color[0] == '#', "Color should start with #")
|
|
})
|
|
}
|
|
}
|
|
|
|
type stubAccountDB struct {
|
|
result *model.Account
|
|
err error
|
|
}
|
|
|
|
func (s *stubAccountDB) GetByEmail(ctx context.Context, email string) (*model.Account, error) {
|
|
return s.result, s.err
|
|
}
|
|
|
|
func (s *stubAccountDB) GetByToken(ctx context.Context, email string) (*model.Account, error) {
|
|
return nil, merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) GetAccountsByRefs(ctx context.Context, orgRef primitive.ObjectID, refs []primitive.ObjectID) ([]model.Account, error) {
|
|
return nil, merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) Create(ctx context.Context, object *model.Account) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) InsertMany(ctx context.Context, objects []*model.Account) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) Get(ctx context.Context, objectRef primitive.ObjectID, result *model.Account) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) Update(ctx context.Context, object *model.Account) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) Patch(ctx context.Context, objectRef primitive.ObjectID, patch builder.Patch) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) Delete(ctx context.Context, objectRef primitive.ObjectID) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) DeleteMany(ctx context.Context, query builder.Query) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) DeleteCascade(ctx context.Context, objectRef primitive.ObjectID) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func (s *stubAccountDB) FindOne(ctx context.Context, query builder.Query, result *model.Account) error {
|
|
return merrors.NotImplemented("stub")
|
|
}
|
|
|
|
func TestEnsureLoginAvailable(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
|
|
t.Run("available", func(t *testing.T) {
|
|
api := &AccountAPI{
|
|
logger: logger,
|
|
db: &stubAccountDB{
|
|
err: merrors.ErrNoData,
|
|
},
|
|
}
|
|
assert.NoError(t, api.ensureLoginAvailable(ctx, "new@example.com"))
|
|
})
|
|
|
|
t.Run("taken", func(t *testing.T) {
|
|
api := &AccountAPI{
|
|
logger: logger,
|
|
db: &stubAccountDB{
|
|
result: &model.Account{},
|
|
},
|
|
}
|
|
err := api.ensureLoginAvailable(ctx, "used@example.com")
|
|
assert.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrDataConflict))
|
|
})
|
|
|
|
t.Run("invalid login", func(t *testing.T) {
|
|
api := &AccountAPI{
|
|
logger: logger,
|
|
db: &stubAccountDB{
|
|
err: merrors.ErrNoData,
|
|
},
|
|
}
|
|
err := api.ensureLoginAvailable(ctx, " ")
|
|
assert.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrInvalidArg))
|
|
})
|
|
|
|
t.Run("db error", func(t *testing.T) {
|
|
api := &AccountAPI{
|
|
logger: logger,
|
|
db: &stubAccountDB{
|
|
err: errors.New("boom"),
|
|
},
|
|
}
|
|
err := api.ensureLoginAvailable(ctx, "err@example.com")
|
|
assert.EqualError(t, err, "boom")
|
|
})
|
|
}
|