134 lines
4.4 KiB
Go
134 lines
4.4 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"nearle/models"
|
|
"nearle/repositories"
|
|
)
|
|
|
|
/*
|
|
An account that is created perfectly and cannot sign in.
|
|
|
|
`weblogin` matches `WHERE authname = ? AND configid = ?`. A form that sends an
|
|
email and a name — which is every form in this console — produces a row with an
|
|
empty authname and a configid of 0, so the sign-in query matches nothing and
|
|
answers "we do not recognise that email".
|
|
|
|
Observed on 1 Sep 2026: a merchant added their own administrator through the
|
|
console, saw it succeed, saw it listed, and was refused at the login screen.
|
|
*/
|
|
|
|
func TestANewAccountCanActuallySignIn(t *testing.T) {
|
|
ready := PrepareNewAccount(models.User{Email: "thiruomart@gmail.com"})
|
|
|
|
if ready.Authname != "thiruomart@gmail.com" {
|
|
t.Errorf("authname was not derived from the email: %q", ready.Authname)
|
|
}
|
|
if ready.Configid != ConsoleConfigID {
|
|
t.Errorf("configid = %d, want %d — 0 matches no console account", ready.Configid, ConsoleConfigID)
|
|
}
|
|
}
|
|
|
|
// A caller with its own sign-in name keeps it. Deriving from the email is a
|
|
// fallback for forms that do not ask, not a rule that overwrites an answer.
|
|
func TestAnExplicitSignInNameIsKept(t *testing.T) {
|
|
ready := PrepareNewAccount(models.User{Authname: "suriya.nsn", Email: "suriya@example.com"})
|
|
if ready.Authname != "suriya.nsn" {
|
|
t.Errorf("an explicit authname was overwritten with %q", ready.Authname)
|
|
}
|
|
}
|
|
|
|
func TestAnExplicitConfigIsKept(t *testing.T) {
|
|
ready := PrepareNewAccount(models.User{Email: "a@b.com", Configid: 4})
|
|
if ready.Configid != 4 {
|
|
t.Errorf("configid = %d, want the caller's 4", ready.Configid)
|
|
}
|
|
}
|
|
|
|
// Whitespace around a pasted email would become an authname nothing can match —
|
|
// the same invisible failure, one space wide.
|
|
func TestASurroundingSpaceDoesNotBecomePartOfTheSignInName(t *testing.T) {
|
|
ready := PrepareNewAccount(models.User{Email: " thiru@omart.com "})
|
|
if ready.Authname != "thiru@omart.com" {
|
|
t.Errorf("authname = %q, want it trimmed", ready.Authname)
|
|
}
|
|
}
|
|
|
|
// An account with no email at all cannot be given a sign-in name, and must not
|
|
// be given a blank one that looks like it has been handled.
|
|
func TestNoEmailLeavesTheSignInNameEmpty(t *testing.T) {
|
|
ready := PrepareNewAccount(models.User{})
|
|
if ready.Authname != "" {
|
|
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)
|
|
}
|
|
}
|