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