170 lines
6.6 KiB
Go
170 lines
6.6 KiB
Go
package routes_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"doormile/db"
|
|
"doormile/internal/testpg"
|
|
"doormile/models"
|
|
"doormile/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Client onboarding with an address, end to end through the real router and
|
|
// handlers against a real Postgres. Skipped unless REGISTRY_TEST_DSN is set;
|
|
// the DSN must be a THROWAWAY database — these tables are dropped and
|
|
// recreated in their own schema.
|
|
|
|
func onboardingDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
dsn := os.Getenv("REGISTRY_TEST_DSN")
|
|
if dsn == "" {
|
|
t.Skip("REGISTRY_TEST_DSN not set; skipping Postgres onboarding test")
|
|
}
|
|
gdb := testpg.Open(t, dsn, "client_onboarding_routes_test")
|
|
all := []any{&models.Tenant{}, &models.DoormileAuth{}, &models.AppUser{}, &models.TenantLocation{}, &models.AppLocation{}}
|
|
if err := gdb.Migrator().DropTable(all...); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := gdb.AutoMigrate(all...); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Put the previous handle back, nil included: other tests in this package
|
|
// rely on there being no database (the gate tests expect the handler to
|
|
// fail without one, not to query a closed test schema).
|
|
prev := db.DB
|
|
db.DB = gdb
|
|
t.Cleanup(func() { db.DB = prev })
|
|
// The owner must exist as Doormile staff (onboardingOwnerStillValid).
|
|
hash, _ := utils.HashPassword("owner-pass-123")
|
|
if err := gdb.Create(&models.DoormileAuth{Email: onboardingOwner, PasswordHash: hash, Role: "admin"}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := gdb.Create(&models.AppLocation{Applocationid: 1, Applocationname: "Coimbatore", Status: "Active"}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return gdb
|
|
}
|
|
|
|
func onboardBody(extra string) string {
|
|
return `{"companyname":"Peelamedu Provisions","contactname":"Priya Raman","email":"ops@peelamedu.test",
|
|
"phone":"9876543210","password":"Strong-pass-1","applocationid":1` + extra + `}`
|
|
}
|
|
|
|
const goodAddress = `,"address":"14 DB Road, RS Puram, Coimbatore","city":"Coimbatore","state":"Tamil Nadu",
|
|
"pincode":"641002","latitude":11.0090,"longitude":76.9500`
|
|
|
|
func TestOnboardingRequiresAnAddressWithAMapLocation(t *testing.T) {
|
|
gdb := onboardingDB(t)
|
|
app := onboardingApp()
|
|
tok := consoleToken(t, onboardingOwner, 1, 0)
|
|
|
|
cases := []struct{ name, extra, want string }{
|
|
{"no address", "", "enter the client's address"},
|
|
{"bad pincode", `,"address":"14 DB Road, RS Puram","pincode":"64100","latitude":11.0,"longitude":76.9`, "6-digit pincode"},
|
|
{"typed, not picked", `,"address":"14 DB Road, RS Puram","pincode":"641002"`, "pick the address from the suggestions"},
|
|
}
|
|
for _, c := range cases {
|
|
code, body := do(t, app, http.MethodPost, "/api/v1/admin/clients/onboard", tok, onboardBody(c.extra))
|
|
if code != 400 || !strings.Contains(body, c.want) {
|
|
t.Errorf("%s: %d %s, want 400 containing %q", c.name, code, body, c.want)
|
|
}
|
|
}
|
|
var n int64
|
|
gdb.Model(&models.Tenant{}).Count(&n)
|
|
if n != 0 {
|
|
t.Fatal("a refused onboarding must create nothing")
|
|
}
|
|
}
|
|
|
|
func TestOnboardingSavesTheAddressAsThePrimaryLocation(t *testing.T) {
|
|
gdb := onboardingDB(t)
|
|
app := onboardingApp()
|
|
tok := consoleToken(t, onboardingOwner, 1, 0)
|
|
|
|
code, body := do(t, app, http.MethodPost, "/api/v1/admin/clients/onboard", tok, onboardBody(goodAddress))
|
|
if code != 201 {
|
|
t.Fatalf("onboard = %d %s", code, body)
|
|
}
|
|
var loc models.TenantLocation
|
|
if err := gdb.First(&loc).Error; err != nil {
|
|
t.Fatalf("no location created: %v", err)
|
|
}
|
|
if !loc.Isprimary || loc.Address != "14 DB Road, RS Puram, Coimbatore" || loc.Pincode != "641002" ||
|
|
loc.City != "Coimbatore" || loc.Latitude != 11.009 || loc.Locationname != "Peelamedu Provisions" {
|
|
t.Fatalf("location saved as %+v", loc)
|
|
}
|
|
|
|
// The list shows it.
|
|
code, body = do(t, app, http.MethodGet, "/api/v1/admin/clients/onboarded", tok, "")
|
|
if code != 200 || !strings.Contains(body, `"pincode":"641002"`) || !strings.Contains(body, `"address":"14 DB Road, RS Puram, Coimbatore"`) {
|
|
t.Fatalf("list = %d %s", code, body)
|
|
}
|
|
|
|
// The client signs in; the login now carries their city for the zone list.
|
|
code, body = do(t, app, http.MethodPost, "/api/v1/admin/login", "", `{"email":"ops@peelamedu.test","password":"Strong-pass-1"}`)
|
|
if code != 200 {
|
|
t.Fatalf("client login = %d %s", code, body)
|
|
}
|
|
var login struct {
|
|
User struct {
|
|
Applocationid int `json:"applocationid"`
|
|
Tenantid *int `json:"tenantid"`
|
|
} `json:"user"`
|
|
}
|
|
if err := json.Unmarshal([]byte(body), &login); err != nil || login.User.Applocationid != 1 || login.User.Tenantid == nil {
|
|
t.Fatalf("login user = %+v (%v)", login.User, err)
|
|
}
|
|
}
|
|
|
|
func TestEditingTheAddressUpdatesOrCreatesTheMainLocation(t *testing.T) {
|
|
gdb := onboardingDB(t)
|
|
app := onboardingApp()
|
|
tok := consoleToken(t, onboardingOwner, 1, 0)
|
|
|
|
if code, body := do(t, app, http.MethodPost, "/api/v1/admin/clients/onboard", tok, onboardBody(goodAddress)); code != 201 {
|
|
t.Fatalf("onboard = %d %s", code, body)
|
|
}
|
|
var auth models.DoormileAuth
|
|
if err := gdb.Where("email = ?", "ops@peelamedu.test").First(&auth).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
move := `{"location":{"address":"5 Avinashi Road, Peelamedu","city":"Coimbatore","state":"Tamil Nadu",
|
|
"pincode":"641004","latitude":11.0300,"longitude":76.9900}}`
|
|
if code, body := do(t, app, http.MethodPut, fmt.Sprintf("/api/v1/admin/clients/%d", auth.ID), tok, move); code != 200 {
|
|
t.Fatalf("edit = %d %s", code, body)
|
|
}
|
|
var locs []models.TenantLocation
|
|
gdb.Find(&locs)
|
|
if len(locs) != 1 || locs[0].Address != "5 Avinashi Road, Peelamedu" || locs[0].Pincode != "641004" || !locs[0].Isprimary {
|
|
t.Fatalf("after edit: %+v", locs)
|
|
}
|
|
|
|
// An edit with a bad address is refused and changes nothing.
|
|
bad := `{"location":{"address":"x","pincode":"641004","latitude":11.03,"longitude":76.99}}`
|
|
if code, _ := do(t, app, http.MethodPut, fmt.Sprintf("/api/v1/admin/clients/%d", auth.ID), tok, bad); code != 400 {
|
|
t.Fatalf("bad address edit = %d, want 400", code)
|
|
}
|
|
|
|
// A client onboarded before addresses existed: the edit creates the location.
|
|
hash, _ := utils.HashPassword("Old-pass-123")
|
|
old := models.Tenant{Tenantname: "Old Client", Primaryemail: "old@client.test", Primarycontact: "9876500000", Status: "Active"}
|
|
gdb.Create(&old)
|
|
tid := old.Tenantid
|
|
oldAuth := models.DoormileAuth{Email: "old@client.test", PasswordHash: hash, Role: "manager", Tenantid: &tid}
|
|
gdb.Create(&oldAuth)
|
|
if code, body := do(t, app, http.MethodPut, fmt.Sprintf("/api/v1/admin/clients/%d", oldAuth.ID), tok, move); code != 200 {
|
|
t.Fatalf("edit old client = %d %s", code, body)
|
|
}
|
|
var created models.TenantLocation
|
|
if err := gdb.Where("tenantid = ?", tid).First(&created).Error; err != nil || !created.Isprimary || created.Locationname != "Old Client" {
|
|
t.Fatalf("old client location = %+v (%v)", created, err)
|
|
}
|
|
}
|