package controllers import ( "strings" "sync" "testing" "time" "gorm.io/gorm/schema" ) func validOnboarding() onboardClientRequest { return onboardClientRequest{ Companyname: " Acme Foods ", Contactname: "Priya Raman", Email: " Ops@Acme.Example ", Phone: "+91 98765-43210", Password: "s3cure-pass", Applocationid: 1, } } func TestOnboardingValidateNormalises(t *testing.T) { r := validOnboarding() if msg := r.validate(); msg != "" { t.Fatalf("valid request refused: %s", msg) } if r.Companyname != "Acme Foods" || r.Contactname != "Priya Raman" || r.Email != "ops@acme.example" || r.Phone != "9876543210" { t.Fatalf("not normalised: %+v", r) } } func TestOnboardingValidateRefuses(t *testing.T) { cases := map[string]func(*onboardClientRequest){ "company name is required": func(r *onboardClientRequest) { r.Companyname = " " }, "company name is too long": func(r *onboardClientRequest) { r.Companyname = strings.Repeat("a", 121) }, "contact person's name": func(r *onboardClientRequest) { r.Contactname = "" }, "valid email": func(r *onboardClientRequest) { r.Email = "not-an-email" }, "valid email ": func(r *onboardClientRequest) { r.Email = "Ops " }, "valid email ": func(r *onboardClientRequest) { r.Email = "ops@localhost" }, "10-digit mobile": func(r *onboardClientRequest) { r.Phone = "12345" }, "10-digit mobile ": func(r *onboardClientRequest) { r.Phone = "5876543210" }, // must start 6-9 "at least 8 characters": func(r *onboardClientRequest) { r.Password = "short" }, "at most 72 characters": func(r *onboardClientRequest) { r.Password = strings.Repeat("x", 73) }, "must not be the email": func(r *onboardClientRequest) { r.Password = "OPS@acme.example" }, "must not be the email or the ": func(r *onboardClientRequest) { r.Password = "9876543210" }, "operating city": func(r *onboardClientRequest) { r.Applocationid = 0 }, } for want, mutate := range cases { r := validOnboarding() mutate(&r) if msg := r.validate(); !strings.Contains(msg, strings.TrimSpace(want)) { t.Errorf("%q: got %q", want, msg) } } } func TestNormalisePhone(t *testing.T) { for in, want := range map[string]string{ "9876543210": "9876543210", "+919876543210": "9876543210", "919876543210": "9876543210", "09876543210": "9876543210", " 98765 43210 ": "9876543210", "(987) 654-3210": "9876543210", } { if got := normalisePhone(in); got != want { t.Errorf("normalisePhone(%q) = %q, want %q", in, got, want) } } } // The list query selects these column aliases; every one must land in a field. // GORM maps silently — a field it cannot see stays empty with no error — so // this parses the scan struct exactly as GORM does and checks each alias. func TestOnboardedClientRowMapsEveryColumn(t *testing.T) { s, err := schema.Parse(&onboardedClientRow{}, &sync.Map{}, schema.NamingStrategy{}) if err != nil { t.Fatal(err) } for _, col := range []string{ "authid", "tenantid", "tenantname", "primaryemail", "primarycontact", "status", "requiredeliveryotp", "contactname", "loginemail", "loginrole", "authcreatedat", "tenantcreatedat", } { if s.LookUpField(col) == nil { t.Errorf("column %q selected by the list query maps to no field", col) } } } func TestOnboardedClientRowToClient(t *testing.T) { zero := time.Time{} // As the driver hands back a timestamp-without-zone column: IST digits tagged UTC. tenant := time.Date(2026, 6, 24, 16, 14, 0, 0, time.UTC) c := onboardedClientRow{Authid: 7, Tenantname: "Acme", Loginemail: "a@b.co", Authcreatedat: &zero, Tenantcreatedat: &tenant}.toClient() if c.Authid != 7 || c.Tenantname != "Acme" || c.Loginemail != "a@b.co" { t.Fatalf("fields lost: %+v", c) } // 16:14 IST, i.e. 10:44 UTC — not 16:14 UTC (which would show as 21:44 in India). if c.Logincreatedat == nil || !c.Logincreatedat.Equal(time.Date(2026, 6, 24, 10, 44, 0, 0, time.UTC)) { t.Fatalf("a zero login date must fall back to the tenant's, read as IST: %v", c.Logincreatedat) } if (onboardedClientRow{}).toClient().Logincreatedat != nil { t.Fatal("no real date must give null, not year 1") } }