From 1e3fe88e87ab65068e8d830066cac4cf20cc9050 Mon Sep 17 00:00:00 2001 From: abhishek Date: Tue, 1 Sep 2026 13:53:22 +0530 Subject: [PATCH] user accounts --- services/newAccount_test.go | 69 +++++++++++++++++++++++++++++++++++++ services/userService.go | 6 ++++ 2 files changed, 75 insertions(+) diff --git a/services/newAccount_test.go b/services/newAccount_test.go index 731ac14..cee720d 100644 --- a/services/newAccount_test.go +++ b/services/newAccount_test.go @@ -4,6 +4,7 @@ import ( "testing" "nearle/models" + "nearle/repositories" ) /* @@ -62,3 +63,71 @@ func TestNoEmailLeavesTheSignInNameEmpty(t *testing.T) { t.Errorf("authname = %q, want empty", ready.Authname) } } + +/* +The wiring, not the function. + +PrepareNewAccount was written, tested and committed — and never called from +`users/create`. The unit tests above all passed, because they exercise the +function directly; nothing asserted that the creation path actually used it. So +four deployments shipped a fix that was not reachable, and the bug it fixes was +still reproducible in production every time. + +These call the SERVICE and inspect what reaches the repository, which is the +only thing that can tell the two apart. +*/ + +type recordingUserRepo struct { + repositories.UserRepository + created models.User +} + +func (r *recordingUserRepo) CreateUser(user models.User) (int, error) { + r.created = user + return 4242, nil +} + +func (r *recordingUserRepo) GetUserById(uid int) (models.UserInfo, error) { + return models.UserInfo{Userid: uid}, nil +} + +func TestCreateUserActuallyPreparesTheAccount(t *testing.T) { + repo := &recordingUserRepo{} + if _, err := NewUserService(repo).CreateUser(models.User{ + Email: "thiruomart@gmail.com", + }); err != nil { + t.Fatalf("CreateUser: %v", err) + } + + if repo.created.Authname != "thiruomart@gmail.com" { + t.Errorf("the account reached the repository with authname %q — PrepareNewAccount is not wired in", + repo.created.Authname) + } + if repo.created.Configid != ConsoleConfigID { + t.Errorf("the account reached the repository with configid %d, want %d", + repo.created.Configid, ConsoleConfigID) + } +} + +type recordingTenantRepo struct { + repositories.TenantRepository + created models.User +} + +func (r *recordingTenantRepo) CreateStaff(user models.User) error { + r.created = user + return nil +} + +// The other creation path. Both make back-office accounts, so both have to +// prepare them — and only one of them did. +func TestCreateStaffActuallyPreparesTheAccount(t *testing.T) { + repo := &recordingTenantRepo{} + if err := NewTenantService(repo).CreateStaff(models.User{Email: "suriya@example.com"}); err != nil { + t.Fatalf("CreateStaff: %v", err) + } + if repo.created.Authname != "suriya@example.com" || repo.created.Configid != ConsoleConfigID { + t.Errorf("CreateStaff did not prepare the account: authname=%q configid=%d", + repo.created.Authname, repo.created.Configid) + } +} diff --git a/services/userService.go b/services/userService.go index 36125c2..3a8589d 100644 --- a/services/userService.go +++ b/services/userService.go @@ -167,6 +167,12 @@ func (s *userService) AppLogin(user models.User) (models.TenantUserInfo, fiber.M } func (s *userService) CreateUser(user models.User) (models.UserInfo, error) { + // Without an authname and a console configid the account is created, + // listed, and then refused at the login screen: `weblogin` matches + // `WHERE authname = ? AND configid = ?` and never looks at the email + // column. See PrepareNewAccount. + user = PrepareNewAccount(user) + // Call repository to create user userid, err := s.repo.CreateUser(user) if err != nil {