package hotline import ( "fmt" "github.com/stretchr/testify/mock" "gopkg.in/yaml.v3" "os" "path" "path/filepath" "sync" ) type AccountManager interface { Create(account Account) error Update(account Account, newLogin string) error Get(login string) *Account List() []Account Delete(login string) error } type YAMLAccountManager struct { accounts map[string]Account accountDir string mu sync.Mutex } func NewYAMLAccountManager(accountDir string) (*YAMLAccountManager, error) { accountMgr := YAMLAccountManager{ accountDir: accountDir, accounts: make(map[string]Account), } matches, err := filepath.Glob(filepath.Join(accountDir, "*.yaml")) if err != nil { return nil, err } if len(matches) == 0 { return nil, fmt.Errorf("no accounts found in directory: %s", accountDir) } for _, file := range matches { var account Account if err = loadFromYAMLFile(file, &account); err != nil { return nil, fmt.Errorf("error loading account %s: %w", file, err) } accountMgr.accounts[account.Login] = account } return &accountMgr, nil } func (am *YAMLAccountManager) Create(account Account) error { am.mu.Lock() defer am.mu.Unlock() // Create account file, returning an error if one already exists. file, err := os.OpenFile( filepath.Join(am.accountDir, path.Join("/", account.Login+".yaml")), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644, ) if err != nil { return fmt.Errorf("error creating account file: %w", err) } defer file.Close() b, err := yaml.Marshal(account) if err != nil { return fmt.Errorf("marshal account to YAML: %v", err) } _, err = file.Write(b) if err != nil { return fmt.Errorf("write account file: %w", err) } am.accounts[account.Login] = account return nil } func (am *YAMLAccountManager) Update(account Account, newLogin string) error { am.mu.Lock() defer am.mu.Unlock() // If the login has changed, rename the account file. if account.Login != newLogin { err := os.Rename( filepath.Join(am.accountDir, path.Join("/", account.Login)+".yaml"), filepath.Join(am.accountDir, path.Join("/", newLogin)+".yaml"), ) if err != nil { return fmt.Errorf("error renaming account file: %w", err) } account.Login = newLogin am.accounts[newLogin] = account delete(am.accounts, account.Login) } out, err := yaml.Marshal(&account) if err != nil { return err } if err := os.WriteFile(filepath.Join(am.accountDir, newLogin+".yaml"), out, 0644); err != nil { return fmt.Errorf("error writing account file: %w", err) } am.accounts[account.Login] = account return nil } func (am *YAMLAccountManager) Get(login string) *Account { am.mu.Lock() defer am.mu.Unlock() account, ok := am.accounts[login] if !ok { return nil } return &account } func (am *YAMLAccountManager) List() []Account { am.mu.Lock() defer am.mu.Unlock() var accounts []Account for _, account := range am.accounts { accounts = append(accounts, account) } return accounts } func (am *YAMLAccountManager) Delete(login string) error { am.mu.Lock() defer am.mu.Unlock() err := os.Remove(filepath.Join(am.accountDir, path.Join("/", login+".yaml"))) if err != nil { return fmt.Errorf("delete account file: %v", err) } delete(am.accounts, login) return nil } type MockAccountManager struct { mock.Mock } func (m *MockAccountManager) Create(account Account) error { args := m.Called(account) return args.Error(0) } func (m *MockAccountManager) Update(account Account, newLogin string) error { args := m.Called(account, newLogin) return args.Error(0) } func (m *MockAccountManager) Get(login string) *Account { args := m.Called(login) return args.Get(0).(*Account) } func (m *MockAccountManager) List() []Account { args := m.Called() return args.Get(0).([]Account) } func (m *MockAccountManager) Delete(login string) error { args := m.Called(login) return args.Error(0) }