package account import ( "arimelody-web/model" repository "arimelody-web/repository/account" "log" "os" "testing" "gotest.tools/v3/assert" ) var ( service *AccountService ) func init() { devNullFile, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666) if err != nil { panic(err) } defer devNullFile.Close() repo := repository.NewAccountRepositoryMemory(make([]*model.Account, 0)) service = NewAccountService( repo, log.New(devNullFile, "", model.DEFAULT_LOG_FLAGS), ) } // Tests the account lifecycle. Users: // - [x] can create account // - [x] can't create duplicate account // - [x] can change username // - [x] can change password // - [x] can change email // - [x] can change avatar URL // - [x] can increment auth failures // - [x] can reset auth failures // - [x] can lock account // - [x] can unlock account // - [x] can delete account func Test_Account(t *testing.T) { username := "testificate" password := "the amazing digital data breach" email := "goober@arimelody.space" avatarURL := "/img/default-avatar.webp" var id string var err error t.Run("accounts should start empty", func(t *testing.T) { t.Run("count is zero", func(t *testing.T) { if num, err := service.GetCount(); err != nil { t.Errorf("Failed to get number of accounts: %v", err) } else { assert.Equal(t, num, 0) } }) t.Run("service returns empty array", func(t *testing.T) { if accounts, err := service.GetAll(); err != nil { t.Errorf("Failed to get number of accounts: %v", err) } else { assert.Equal(t, len(accounts), 0) } }) }) t.Run("can create account", func(t *testing.T) { id, err = service.Create(username, password, &email, &avatarURL) if err != nil { t.Errorf("Failed to create account: %v", err) } t.Run("and fetch by ID", func(t *testing.T) { account, err := service.GetByID(id) if err != nil { t.Errorf("Failed to get account after creation: %v", err) } assert.Equal(t, account.Username, username) assert.Equal(t, account.Password, password) assert.Equal(t, account.Email.String, email) assert.Equal(t, account.AvatarURL.String, avatarURL) assert.Equal(t, account.FailAttempts, 0) assert.Equal(t, account.Locked, false) }) t.Run("and fetch by username", func(t *testing.T) { account, err := service.GetByUsername(username) if err != nil { t.Errorf("Failed to get account after creation: %v", err) } assert.Equal(t, account.ID, id) assert.Equal(t, account.Password, password) assert.Equal(t, account.Email.String, email) assert.Equal(t, account.AvatarURL.String, avatarURL) assert.Equal(t, account.FailAttempts, 0) assert.Equal(t, account.Locked, false) }) t.Run("and fetch by email", func(t *testing.T) { account, err := service.GetByEmail(email) if err != nil { t.Errorf("Failed to get account after creation: %v", err) } assert.Equal(t, account.ID, id) assert.Equal(t, account.Username, username) assert.Equal(t, account.Password, password) assert.Equal(t, account.AvatarURL.String, avatarURL) assert.Equal(t, account.FailAttempts, 0) assert.Equal(t, account.Locked, false) }) }) t.Run("number of accounts should increment", func(t *testing.T) { t.Run("count is one", func(t *testing.T) { if num, err := service.GetCount(); err != nil { t.Errorf("Failed to get number of accounts: %v", err) } else { assert.Equal(t, num, 1) } }) t.Run("service returns array with one account", func(t *testing.T) { if accounts, err := service.GetAll(); err != nil { t.Errorf("Failed to get number of accounts: %v", err) } else { assert.Equal(t, len(accounts), 1) } }) }) t.Run("can't create duplicate account", func(t *testing.T) { _, err := service.Create(username, password, &email, &avatarURL) if err == nil { t.Error("Duplicate account was created") } }) t.Run("can change username", func(t *testing.T) { testUsername := "some_other_name" if err := service.ChangeUsername(id, testUsername); err != nil { t.Errorf("Failed to change username: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if account.Username != testUsername { t.Error("Username did not update") } t.Run("but not to an invalid value", func(t *testing.T) { if err := service.ChangeUsername(id, ""); err == nil { t.Error("Could change username to invalid value") } }) }) t.Run("can change password", func(t *testing.T) { testPassword := "other more different password" if err := service.ChangePassword(id, testPassword); err != nil { t.Errorf("Failed to change password: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if account.Password != testPassword { t.Error("Password did not update") } t.Run("but not to an invalid value", func(t *testing.T) { if err := service.ChangePassword(id, ""); err == nil { t.Error("Could change password to invalid value") } }) }) t.Run("can change email", func(t *testing.T) { testEmail := "brandnewemail@for.me" if err := service.ChangeEmail(id, testEmail); err != nil { t.Errorf("Failed to change email: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if !account.Email.Valid || account.Email.String != testEmail { t.Error("Email did not update") } }) t.Run("can remove email", func(t *testing.T) { if err := service.ChangeEmail(id, ""); err != nil { t.Errorf("Failed to change email: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if account.Email.Valid || len(account.Email.String) > 0 { t.Error("Email did not update") } }) t.Run("can change avatar URL", func(t *testing.T) { testAvatarURL := "/img/some-other-avatar.webp" if err := service.ChangeAvatarURL(id, testAvatarURL); err != nil { t.Errorf("Failed to change avatar URL: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if !account.AvatarURL.Valid || account.AvatarURL.String != testAvatarURL { t.Error("Avatar URL did not update") } }) t.Run("can remove avatar URL", func(t *testing.T) { if err := service.ChangeAvatarURL(id, ""); err != nil { t.Errorf("Failed to change avatar URL: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else if account.AvatarURL.Valid || len(account.AvatarURL.String) > 0 { t.Error("Avatar URL did not update") } }) t.Run("can increment auth failures", func(t *testing.T) { if num, err := service.IncrementFails(id); err != nil { t.Errorf("Failed to increment account auth failures: %v", err) } else { assert.Equal(t, num, 1) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else { assert.Equal(t, account.FailAttempts, 1) } }) t.Run("can reset auth failures", func(t *testing.T) { if err := service.ResetFails(id); err != nil { t.Errorf("Failed to reset account auth failures: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else { assert.Equal(t, account.FailAttempts, 0) } }) t.Run("can lock account", func(t *testing.T) { if err := service.Lock(id); err != nil { t.Errorf("Failed to lock account: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else { assert.Equal(t, account.Locked, true) } }) t.Run("can unlock account", func(t *testing.T) { if err := service.Unlock(id); err != nil { t.Errorf("Failed to unlock account: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account: %v", err) } else if account == nil { t.Error("Account is nil after update") } else { assert.Equal(t, account.Locked, false) } }) t.Run("can delete account", func(t *testing.T) { if err = service.Delete(id); err != nil { t.Errorf("Failed to delete account: %v", err) } if account, err := service.GetByID(id); err != nil { t.Errorf("Failed to get account after deletion: %v", err) } else if account != nil { t.Error("Account still exists after deletion") } }) t.Run("can't create an account with invalid", func(t *testing.T) { t.Run("username", func(t *testing.T) { if _, err := service.Create("", password, &email, &avatarURL); err == nil { t.Error("Could create account with empty username") } }) t.Run("password", func(t *testing.T) { if _, err := service.Create(username, "", &email, &avatarURL); err == nil { t.Error("Could create account with empty password") } }) t.Run("email", func(t *testing.T) { testEmail := "" if _, err := service.Create(username, password, &testEmail, &avatarURL); err == nil { t.Error("Could create account with empty (non-nil) email") } }) }) t.Run("can't update account that doesn't exist", func(t *testing.T) { garbageAccountID := "adsginh534g9405gmb40i9bm" t.Run("username", func(t *testing.T) { if err := service.ChangeUsername(garbageAccountID, "some-username"); err == nil { t.Error("Could update non-existent account's username") } }) t.Run("password", func(t *testing.T) { if err := service.ChangePassword(garbageAccountID, "some-password"); err == nil { t.Error("Could update non-existent account's password") } }) t.Run("email", func(t *testing.T) { if err := service.ChangeEmail(garbageAccountID, "some-email@real.gov"); err == nil { t.Error("Could update non-existent account's email") } }) t.Run("avatar URL", func(t *testing.T) { if err := service.ChangeAvatarURL(garbageAccountID, "/img/null.webp"); err == nil { t.Error("Could update non-existent account's avatar URL") } }) }) t.Cleanup(func() { if err := service.Delete(id); err != nil { t.Errorf("Failed to clean up test case: %v", err) } }) }