rwadurian/backend/mpc-system/tests/integration/account/repository_test.go

437 lines
12 KiB
Go

//go:build integration
package integration_test
import (
"context"
"database/sql"
"os"
"testing"
"github.com/google/uuid"
_ "github.com/lib/pq"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/rwadurian/mpc-system/services/account/adapters/output/postgres"
"github.com/rwadurian/mpc-system/services/account/domain/entities"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)
type AccountRepositoryTestSuite struct {
suite.Suite
db *sql.DB
accountRepo *postgres.AccountPostgresRepo
shareRepo *postgres.AccountSharePostgresRepo
recoveryRepo *postgres.RecoverySessionPostgresRepo
ctx context.Context
}
func TestAccountRepositorySuite(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
suite.Run(t, new(AccountRepositoryTestSuite))
}
func (s *AccountRepositoryTestSuite) SetupSuite() {
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
dsn = "postgres://mpc_user:mpc_password@localhost:5433/mpc_system_test?sslmode=disable"
}
var err error
s.db, err = sql.Open("postgres", dsn)
require.NoError(s.T(), err)
err = s.db.Ping()
require.NoError(s.T(), err, "Failed to connect to test database")
s.accountRepo = postgres.NewAccountPostgresRepo(s.db).(*postgres.AccountPostgresRepo)
s.shareRepo = postgres.NewAccountSharePostgresRepo(s.db).(*postgres.AccountSharePostgresRepo)
s.recoveryRepo = postgres.NewRecoverySessionPostgresRepo(s.db).(*postgres.RecoverySessionPostgresRepo)
s.ctx = context.Background()
}
func (s *AccountRepositoryTestSuite) TearDownSuite() {
if s.db != nil {
s.db.Close()
}
}
func (s *AccountRepositoryTestSuite) SetupTest() {
s.cleanupTestData()
}
func (s *AccountRepositoryTestSuite) cleanupTestData() {
s.db.ExecContext(s.ctx, "DELETE FROM account_recovery_sessions WHERE account_id IN (SELECT id FROM accounts WHERE username LIKE 'test_%')")
s.db.ExecContext(s.ctx, "DELETE FROM account_shares WHERE account_id IN (SELECT id FROM accounts WHERE username LIKE 'test_%')")
s.db.ExecContext(s.ctx, "DELETE FROM accounts WHERE username LIKE 'test_%'")
}
func (s *AccountRepositoryTestSuite) TestCreateAccount() {
account := entities.NewAccount(
"test_user_1",
"test1@example.com",
[]byte("test-public-key-1"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
// Verify account was created
retrieved, err := s.accountRepo.GetByID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Equal(s.T(), account.Username, retrieved.Username)
assert.Equal(s.T(), account.Email, retrieved.Email)
assert.Equal(s.T(), account.ThresholdN, retrieved.ThresholdN)
assert.Equal(s.T(), account.ThresholdT, retrieved.ThresholdT)
}
func (s *AccountRepositoryTestSuite) TestGetByUsername() {
account := entities.NewAccount(
"test_user_2",
"test2@example.com",
[]byte("test-public-key-2"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
retrieved, err := s.accountRepo.GetByUsername(s.ctx, "test_user_2")
require.NoError(s.T(), err)
assert.True(s.T(), account.ID.Equals(retrieved.ID))
}
func (s *AccountRepositoryTestSuite) TestGetByEmail() {
account := entities.NewAccount(
"test_user_3",
"test3@example.com",
[]byte("test-public-key-3"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
retrieved, err := s.accountRepo.GetByEmail(s.ctx, "test3@example.com")
require.NoError(s.T(), err)
assert.True(s.T(), account.ID.Equals(retrieved.ID))
}
func (s *AccountRepositoryTestSuite) TestUpdateAccount() {
account := entities.NewAccount(
"test_user_4",
"test4@example.com",
[]byte("test-public-key-4"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
// Update account
phone := "+1234567890"
account.Phone = &phone
account.Status = value_objects.AccountStatusSuspended
err = s.accountRepo.Update(s.ctx, account)
require.NoError(s.T(), err)
// Verify update
retrieved, err := s.accountRepo.GetByID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Equal(s.T(), "+1234567890", *retrieved.Phone)
assert.Equal(s.T(), value_objects.AccountStatusSuspended, retrieved.Status)
}
func (s *AccountRepositoryTestSuite) TestExistsByUsername() {
account := entities.NewAccount(
"test_user_5",
"test5@example.com",
[]byte("test-public-key-5"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
exists, err := s.accountRepo.ExistsByUsername(s.ctx, "test_user_5")
require.NoError(s.T(), err)
assert.True(s.T(), exists)
exists, err = s.accountRepo.ExistsByUsername(s.ctx, "nonexistent_user")
require.NoError(s.T(), err)
assert.False(s.T(), exists)
}
func (s *AccountRepositoryTestSuite) TestExistsByEmail() {
account := entities.NewAccount(
"test_user_6",
"test6@example.com",
[]byte("test-public-key-6"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
exists, err := s.accountRepo.ExistsByEmail(s.ctx, "test6@example.com")
require.NoError(s.T(), err)
assert.True(s.T(), exists)
exists, err = s.accountRepo.ExistsByEmail(s.ctx, "nonexistent@example.com")
require.NoError(s.T(), err)
assert.False(s.T(), exists)
}
func (s *AccountRepositoryTestSuite) TestListAccounts() {
// Create multiple accounts
for i := 0; i < 5; i++ {
account := entities.NewAccount(
"test_user_list_"+string(rune('a'+i)),
"testlist"+string(rune('a'+i))+"@example.com",
[]byte("test-public-key-list-"+string(rune('a'+i))),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
}
accounts, err := s.accountRepo.List(s.ctx, 0, 10)
require.NoError(s.T(), err)
assert.GreaterOrEqual(s.T(), len(accounts), 5)
count, err := s.accountRepo.Count(s.ctx)
require.NoError(s.T(), err)
assert.GreaterOrEqual(s.T(), count, int64(5))
}
func (s *AccountRepositoryTestSuite) TestDeleteAccount() {
account := entities.NewAccount(
"test_user_delete",
"testdelete@example.com",
[]byte("test-public-key-delete"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
err = s.accountRepo.Delete(s.ctx, account.ID)
require.NoError(s.T(), err)
_, err = s.accountRepo.GetByID(s.ctx, account.ID)
assert.Error(s.T(), err)
}
// Account Share Tests
func (s *AccountRepositoryTestSuite) TestCreateAccountShare() {
account := entities.NewAccount(
"test_user_share_1",
"testshare1@example.com",
[]byte("test-public-key-share-1"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
share := entities.NewAccountShare(
account.ID,
value_objects.ShareTypeUserDevice,
"party_1",
0,
)
share.SetDeviceInfo("iOS", "device123")
err = s.shareRepo.Create(s.ctx, share)
require.NoError(s.T(), err)
// Verify share was created
retrieved, err := s.shareRepo.GetByID(s.ctx, share.ID.String())
require.NoError(s.T(), err)
assert.Equal(s.T(), share.PartyID, retrieved.PartyID)
assert.Equal(s.T(), "iOS", *retrieved.DeviceType)
}
func (s *AccountRepositoryTestSuite) TestGetSharesByAccountID() {
account := entities.NewAccount(
"test_user_share_2",
"testshare2@example.com",
[]byte("test-public-key-share-2"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
// Create multiple shares
shareTypes := []value_objects.ShareType{
value_objects.ShareTypeUserDevice,
value_objects.ShareTypeServer,
value_objects.ShareTypeRecovery,
}
for i, st := range shareTypes {
share := entities.NewAccountShare(account.ID, st, "party_"+string(rune('a'+i)), i)
err = s.shareRepo.Create(s.ctx, share)
require.NoError(s.T(), err)
}
shares, err := s.shareRepo.GetByAccountID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Len(s.T(), shares, 3)
}
func (s *AccountRepositoryTestSuite) TestGetActiveSharesByAccountID() {
account := entities.NewAccount(
"test_user_share_3",
"testshare3@example.com",
[]byte("test-public-key-share-3"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
// Create active and inactive shares
activeShare := entities.NewAccountShare(account.ID, value_objects.ShareTypeUserDevice, "party_active", 0)
err = s.shareRepo.Create(s.ctx, activeShare)
require.NoError(s.T(), err)
inactiveShare := entities.NewAccountShare(account.ID, value_objects.ShareTypeServer, "party_inactive", 1)
inactiveShare.Deactivate()
err = s.shareRepo.Create(s.ctx, inactiveShare)
require.NoError(s.T(), err)
activeShares, err := s.shareRepo.GetActiveByAccountID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Len(s.T(), activeShares, 1)
assert.Equal(s.T(), "party_active", activeShares[0].PartyID)
}
func (s *AccountRepositoryTestSuite) TestDeactivateShareByAccountID() {
account := entities.NewAccount(
"test_user_share_4",
"testshare4@example.com",
[]byte("test-public-key-share-4"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
share1 := entities.NewAccountShare(account.ID, value_objects.ShareTypeUserDevice, "party_1", 0)
share2 := entities.NewAccountShare(account.ID, value_objects.ShareTypeServer, "party_2", 1)
err = s.shareRepo.Create(s.ctx, share1)
require.NoError(s.T(), err)
err = s.shareRepo.Create(s.ctx, share2)
require.NoError(s.T(), err)
// Deactivate all shares
err = s.shareRepo.DeactivateByAccountID(s.ctx, account.ID)
require.NoError(s.T(), err)
activeShares, err := s.shareRepo.GetActiveByAccountID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Len(s.T(), activeShares, 0)
}
// Recovery Session Tests
func (s *AccountRepositoryTestSuite) TestCreateRecoverySession() {
account := entities.NewAccount(
"test_user_recovery_1",
"testrecovery1@example.com",
[]byte("test-public-key-recovery-1"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
recovery := entities.NewRecoverySession(account.ID, value_objects.RecoveryTypeDeviceLost)
oldShareType := value_objects.ShareTypeUserDevice
recovery.SetOldShareType(oldShareType)
err = s.recoveryRepo.Create(s.ctx, recovery)
require.NoError(s.T(), err)
// Verify recovery was created
retrieved, err := s.recoveryRepo.GetByID(s.ctx, recovery.ID.String())
require.NoError(s.T(), err)
assert.Equal(s.T(), recovery.RecoveryType, retrieved.RecoveryType)
assert.Equal(s.T(), value_objects.RecoveryStatusRequested, retrieved.Status)
}
func (s *AccountRepositoryTestSuite) TestUpdateRecoverySession() {
account := entities.NewAccount(
"test_user_recovery_2",
"testrecovery2@example.com",
[]byte("test-public-key-recovery-2"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
recovery := entities.NewRecoverySession(account.ID, value_objects.RecoveryTypeDeviceLost)
err = s.recoveryRepo.Create(s.ctx, recovery)
require.NoError(s.T(), err)
// Start keygen
keygenID := uuid.New()
recovery.StartKeygen(keygenID)
err = s.recoveryRepo.Update(s.ctx, recovery)
require.NoError(s.T(), err)
// Verify update
retrieved, err := s.recoveryRepo.GetByID(s.ctx, recovery.ID.String())
require.NoError(s.T(), err)
assert.Equal(s.T(), value_objects.RecoveryStatusInProgress, retrieved.Status)
assert.NotNil(s.T(), retrieved.NewKeygenSessionID)
}
func (s *AccountRepositoryTestSuite) TestGetActiveRecoveryByAccountID() {
account := entities.NewAccount(
"test_user_recovery_3",
"testrecovery3@example.com",
[]byte("test-public-key-recovery-3"),
uuid.New(),
3,
2,
)
err := s.accountRepo.Create(s.ctx, account)
require.NoError(s.T(), err)
// Create active recovery
activeRecovery := entities.NewRecoverySession(account.ID, value_objects.RecoveryTypeDeviceLost)
err = s.recoveryRepo.Create(s.ctx, activeRecovery)
require.NoError(s.T(), err)
retrieved, err := s.recoveryRepo.GetActiveByAccountID(s.ctx, account.ID)
require.NoError(s.T(), err)
assert.Equal(s.T(), activeRecovery.ID, retrieved.ID)
}