updates on the onboardings and hubs patches as well
This commit is contained in:
@@ -321,10 +321,10 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
adminAuth.Put("/profile/password", controllers.AdminChangePassword)
|
||||
|
||||
// App Users Management
|
||||
adminAuth.Get("/users", controllers.GetAppUsers)
|
||||
adminAuth.Post("/users", controllers.CreateAppUser)
|
||||
adminAuth.Put("/users/:id", controllers.UpdateAppUser)
|
||||
adminAuth.Delete("/users/:id", controllers.DeleteAppUser)
|
||||
adminAuth.Get("/users", middlewares.DoormileStaffOnly, controllers.GetAppUsers)
|
||||
adminAuth.Post("/users", middlewares.DoormileStaffOnly, controllers.CreateAppUser)
|
||||
adminAuth.Put("/users/:id", middlewares.DoormileStaffOnly, controllers.UpdateAppUser)
|
||||
adminAuth.Delete("/users/:id", middlewares.DoormileStaffOnly, controllers.DeleteAppUser)
|
||||
|
||||
// Partner management (fleet/rider suppliers — distinct from tenants,
|
||||
// which are the client companies Doormile delivers for)
|
||||
@@ -336,7 +336,7 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
|
||||
// Tenant management
|
||||
adminAuth.Get("/tenants", controllers.GetTenants)
|
||||
adminAuth.Post("/tenants", controllers.CreateTenant)
|
||||
adminAuth.Post("/tenants", middlewares.DoormileStaffOnly, controllers.CreateTenant)
|
||||
adminAuth.Get("/tenants/:id", controllers.GetTenantDetails)
|
||||
adminAuth.Put("/tenants/:id", controllers.UpdateTenant)
|
||||
adminAuth.Delete("/tenants/:id", controllers.DeleteTenant)
|
||||
@@ -373,17 +373,17 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
|
||||
// Hubs
|
||||
adminAuth.Get("/hubs", controllers.GetHubs)
|
||||
adminAuth.Post("/hubs", controllers.CreateHub)
|
||||
adminAuth.Post("/hubs", middlewares.DoormileStaffOnly, controllers.CreateHub)
|
||||
adminAuth.Get("/hubs/:id", controllers.GetHubDetails)
|
||||
adminAuth.Put("/hubs/:id", controllers.UpdateHub)
|
||||
adminAuth.Delete("/hubs/:id", controllers.DeleteHub)
|
||||
adminAuth.Put("/hubs/:id", middlewares.DoormileStaffOnly, controllers.UpdateHub)
|
||||
adminAuth.Delete("/hubs/:id", middlewares.DoormileStaffOnly, controllers.DeleteHub)
|
||||
|
||||
// Vehicles
|
||||
adminAuth.Get("/vehicles", controllers.GetVehicles)
|
||||
adminAuth.Post("/vehicles", controllers.CreateVehicle)
|
||||
adminAuth.Post("/vehicles", middlewares.DoormileStaffOnly, controllers.CreateVehicle)
|
||||
adminAuth.Get("/vehicles/:id", controllers.GetVehicleDetails)
|
||||
adminAuth.Put("/vehicles/:id", controllers.UpdateVehicle)
|
||||
adminAuth.Delete("/vehicles/:id", controllers.DeleteVehicle)
|
||||
adminAuth.Put("/vehicles/:id", middlewares.DoormileStaffOnly, controllers.UpdateVehicle)
|
||||
adminAuth.Delete("/vehicles/:id", middlewares.DoormileStaffOnly, controllers.DeleteVehicle)
|
||||
|
||||
// Milers
|
||||
adminAuth.Get("/milers", controllers.GetMilers)
|
||||
@@ -429,25 +429,25 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
adminAuth.Get("/returns", controllers.GetReturns)
|
||||
|
||||
// Tripsheets
|
||||
adminAuth.Get("/tripsheets", controllers.GetTripsheets)
|
||||
adminAuth.Post("/tripsheets", controllers.CreateTripsheet)
|
||||
adminAuth.Get("/tripsheets/:id", controllers.GetTripsheetDetails)
|
||||
adminAuth.Post("/tripsheets/:id/items", controllers.AddTripsheetItem)
|
||||
adminAuth.Delete("/tripsheets/:id/items/:itemid", controllers.DeleteTripsheetItem)
|
||||
adminAuth.Put("/tripsheets/:id/dispatch", controllers.DispatchTripsheet)
|
||||
adminAuth.Put("/tripsheets/:id/arrive", controllers.ArriveTripsheet)
|
||||
adminAuth.Get("/tripsheets", middlewares.DoormileStaffOnly, controllers.GetTripsheets)
|
||||
adminAuth.Post("/tripsheets", middlewares.DoormileStaffOnly, controllers.CreateTripsheet)
|
||||
adminAuth.Get("/tripsheets/:id", middlewares.DoormileStaffOnly, controllers.GetTripsheetDetails)
|
||||
adminAuth.Post("/tripsheets/:id/items", middlewares.DoormileStaffOnly, controllers.AddTripsheetItem)
|
||||
adminAuth.Delete("/tripsheets/:id/items/:itemid", middlewares.DoormileStaffOnly, controllers.DeleteTripsheetItem)
|
||||
adminAuth.Put("/tripsheets/:id/dispatch", middlewares.DoormileStaffOnly, controllers.DispatchTripsheet)
|
||||
adminAuth.Put("/tripsheets/:id/arrive", middlewares.DoormileStaffOnly, controllers.ArriveTripsheet)
|
||||
|
||||
// Competitor branch survey data (from Enquiry.xlsx Sheet 1)
|
||||
adminAuth.Get("/competitor-branches", controllers.GetCompetitorBranches)
|
||||
adminAuth.Post("/competitor-branches", controllers.CreateCompetitorBranch)
|
||||
adminAuth.Put("/competitor-branches/:id", controllers.UpdateCompetitorBranch)
|
||||
adminAuth.Delete("/competitor-branches/:id", controllers.DeleteCompetitorBranch)
|
||||
adminAuth.Get("/competitor-branches", middlewares.DoormileStaffOnly, controllers.GetCompetitorBranches)
|
||||
adminAuth.Post("/competitor-branches", middlewares.DoormileStaffOnly, controllers.CreateCompetitorBranch)
|
||||
adminAuth.Put("/competitor-branches/:id", middlewares.DoormileStaffOnly, controllers.UpdateCompetitorBranch)
|
||||
adminAuth.Delete("/competitor-branches/:id", middlewares.DoormileStaffOnly, controllers.DeleteCompetitorBranch)
|
||||
|
||||
// Carrier pricing benchmarks (from Enquiry.xlsx Sheet 2)
|
||||
adminAuth.Get("/carrier-pricing", controllers.GetCarrierPricing)
|
||||
adminAuth.Post("/carrier-pricing", controllers.CreateCarrierPricing)
|
||||
adminAuth.Put("/carrier-pricing/:id", controllers.UpdateCarrierPricing)
|
||||
adminAuth.Delete("/carrier-pricing/:id", controllers.DeleteCarrierPricing)
|
||||
adminAuth.Get("/carrier-pricing", middlewares.DoormileStaffOnly, controllers.GetCarrierPricing)
|
||||
adminAuth.Post("/carrier-pricing", middlewares.DoormileStaffOnly, controllers.CreateCarrierPricing)
|
||||
adminAuth.Put("/carrier-pricing/:id", middlewares.DoormileStaffOnly, controllers.UpdateCarrierPricing)
|
||||
adminAuth.Delete("/carrier-pricing/:id", middlewares.DoormileStaffOnly, controllers.DeleteCarrierPricing)
|
||||
|
||||
// Pricing
|
||||
adminAuth.Get("/pricing", controllers.GetPricing)
|
||||
@@ -465,9 +465,9 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
|
||||
// Exceptions
|
||||
adminAuth.Get("/exceptions", controllers.GetExceptions)
|
||||
adminAuth.Post("/exceptions", controllers.CreateException)
|
||||
adminAuth.Post("/exceptions", middlewares.DoormileStaffOnly, controllers.CreateException)
|
||||
adminAuth.Get("/exceptions/:id", controllers.GetExceptionDetails)
|
||||
adminAuth.Put("/exceptions/:id/status", controllers.ResolveException)
|
||||
adminAuth.Put("/exceptions/:id/status", middlewares.DoormileStaffOnly, controllers.ResolveException)
|
||||
|
||||
// AI agent registry (krow_talent_app/docs/agent-platform-plan.md, Phase 1).
|
||||
// Doormile staff only — a partner-tenant login gets 403. Reads are open to
|
||||
|
||||
141
routes/routes_client_fleetops_test.go
Normal file
141
routes/routes_client_fleetops_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"doormile/db"
|
||||
"doormile/internal/testpg"
|
||||
"doormile/models"
|
||||
"doormile/utils"
|
||||
)
|
||||
|
||||
// A client (tenant) login can open the console's Fleet Ops menu, so every
|
||||
// create/edit/delete behind it, and every internal list, must refuse a client
|
||||
// token on the server, not just be hidden in the UI.
|
||||
var staffOnlyFleetRoutes = []struct{ method, path string }{
|
||||
{http.MethodPost, "/api/v1/admin/tenants"},
|
||||
{http.MethodGet, "/api/v1/admin/users"},
|
||||
{http.MethodPost, "/api/v1/admin/users"},
|
||||
{http.MethodPut, "/api/v1/admin/users/1"},
|
||||
{http.MethodDelete, "/api/v1/admin/users/1"},
|
||||
{http.MethodPost, "/api/v1/admin/hubs"},
|
||||
{http.MethodPut, "/api/v1/admin/hubs/1"},
|
||||
{http.MethodDelete, "/api/v1/admin/hubs/1"},
|
||||
{http.MethodPost, "/api/v1/admin/vehicles"},
|
||||
{http.MethodPut, "/api/v1/admin/vehicles/1"},
|
||||
{http.MethodDelete, "/api/v1/admin/vehicles/1"},
|
||||
{http.MethodGet, "/api/v1/admin/tripsheets"},
|
||||
{http.MethodPost, "/api/v1/admin/tripsheets"},
|
||||
{http.MethodGet, "/api/v1/admin/tripsheets/1"},
|
||||
{http.MethodPost, "/api/v1/admin/tripsheets/1/items"},
|
||||
{http.MethodDelete, "/api/v1/admin/tripsheets/1/items/1"},
|
||||
{http.MethodPut, "/api/v1/admin/tripsheets/1/dispatch"},
|
||||
{http.MethodPut, "/api/v1/admin/tripsheets/1/arrive"},
|
||||
{http.MethodGet, "/api/v1/admin/competitor-branches"},
|
||||
{http.MethodPost, "/api/v1/admin/competitor-branches"},
|
||||
{http.MethodPut, "/api/v1/admin/competitor-branches/1"},
|
||||
{http.MethodDelete, "/api/v1/admin/competitor-branches/1"},
|
||||
{http.MethodGet, "/api/v1/admin/carrier-pricing"},
|
||||
{http.MethodPost, "/api/v1/admin/carrier-pricing"},
|
||||
{http.MethodPut, "/api/v1/admin/carrier-pricing/1"},
|
||||
{http.MethodDelete, "/api/v1/admin/carrier-pricing/1"},
|
||||
{http.MethodPost, "/api/v1/admin/exceptions"},
|
||||
{http.MethodPut, "/api/v1/admin/exceptions/1/status"},
|
||||
}
|
||||
|
||||
func TestFleetOpsWritesRefuseAClientLogin(t *testing.T) {
|
||||
app := onboardingApp()
|
||||
client := consoleToken(t, "ops@client.test", 3, 42)
|
||||
for _, r := range staffOnlyFleetRoutes {
|
||||
code, body := do(t, app, r.method, r.path, client, `{}`)
|
||||
if code != http.StatusForbidden || !strings.Contains(body, "Doormile staff only") {
|
||||
t.Errorf("%s %s as a client = %d %s, want 403", r.method, r.path, code, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetOpsGuardsLetStaffThrough(t *testing.T) {
|
||||
app := onboardingApp()
|
||||
staff := consoleToken(t, "ops@doormile.com", 1, 0)
|
||||
for _, r := range staffOnlyFleetRoutes {
|
||||
// Past the guard the handler runs (and, with no database, may fail);
|
||||
// all that matters here is that the guard did not refuse.
|
||||
func() {
|
||||
defer func() { _ = recover() }()
|
||||
if code, body := do(t, app, r.method, r.path, staff, `{}`); strings.Contains(body, "Doormile staff only") {
|
||||
t.Errorf("%s %s as staff = %d %s, the guard refused", r.method, r.path, code, body)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Hubs are scoped to the client's own city, from the server. Postgres-gated
|
||||
// like the other route tests; the DSN must be a THROWAWAY database.
|
||||
func TestClientSeesOnlyItsOwnCityHubs(t *testing.T) {
|
||||
dsn := os.Getenv("REGISTRY_TEST_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("REGISTRY_TEST_DSN not set; skipping Postgres hub scoping test")
|
||||
}
|
||||
gdb := testpg.Open(t, dsn, "client_fleetops_routes_test")
|
||||
all := []any{&models.Hub{}, &models.AppUser{}, &models.AppLocation{}}
|
||||
if err := gdb.Migrator().DropTable(all...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gdb.AutoMigrate(all...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prev := db.DB
|
||||
db.DB = gdb
|
||||
t.Cleanup(func() { db.DB = prev })
|
||||
|
||||
gdb.Create(&models.AppLocation{Applocationid: 1, Applocationname: "Coimbatore", Status: "Active"})
|
||||
gdb.Create(&models.AppLocation{Applocationid: 2, Applocationname: "Chennai", Status: "Active"})
|
||||
cbe := models.Hub{Hubname: "Coimbatore Neptune Hub", Hubtype: "delivery_hub", Applocationid: 1, Status: "Active"}
|
||||
chn := models.Hub{Hubname: "Chennai Guindy Hub", Hubtype: "delivery_hub", Applocationid: 2, Status: "Active"}
|
||||
gdb.Create(&cbe)
|
||||
gdb.Create(&chn)
|
||||
withCity := models.AppUser{Authname: "Priya", Email: "ops@peelamedu.test", Contactno: "9876543210", Password: "x", Roleid: 3, Applocationid: 1}
|
||||
noCity := models.AppUser{Authname: "Nocity", Email: "ops@nocity.test", Contactno: "9876543211", Password: "x", Roleid: 3}
|
||||
gdb.Create(&withCity)
|
||||
gdb.Create(&noCity)
|
||||
|
||||
app := onboardingApp()
|
||||
mint := func(uid int, email string, tenant int) string {
|
||||
tok, err := utils.GenerateToken(uid, email, 3, tenant, 1, jwtSecret)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tok
|
||||
}
|
||||
client := mint(withCity.Userid, withCity.Email, 42)
|
||||
|
||||
code, body := do(t, app, http.MethodGet, "/api/v1/admin/hubs", client, "")
|
||||
if code != 200 || !strings.Contains(body, "Coimbatore Neptune Hub") || strings.Contains(body, "Chennai Guindy Hub") {
|
||||
t.Fatalf("client hub list = %d %s, want Coimbatore only", code, body)
|
||||
}
|
||||
// Asking for another city by query string changes nothing.
|
||||
if _, body := do(t, app, http.MethodGet, "/api/v1/admin/hubs?applocationid=2", client, ""); strings.Contains(body, "Chennai Guindy Hub") {
|
||||
t.Fatalf("?applocationid=2 leaked another city: %s", body)
|
||||
}
|
||||
if code, _ := do(t, app, http.MethodGet, "/api/v1/admin/hubs/"+strconv.Itoa(chn.Hubid), client, ""); code != 404 {
|
||||
t.Fatalf("another city's hub detail = %d, want 404", code)
|
||||
}
|
||||
if code, _ := do(t, app, http.MethodGet, "/api/v1/admin/hubs/"+strconv.Itoa(cbe.Hubid), client, ""); code != 200 {
|
||||
t.Fatalf("own city's hub detail = %d, want 200", code)
|
||||
}
|
||||
|
||||
// A client with no city on file sees no hubs, never every hub.
|
||||
if _, body := do(t, app, http.MethodGet, "/api/v1/admin/hubs", mint(noCity.Userid, noCity.Email, 43), ""); strings.Contains(body, "Hub\"") {
|
||||
t.Fatalf("client without a city = %s, want an empty list", body)
|
||||
}
|
||||
|
||||
// Staff still see every hub.
|
||||
_, body = do(t, app, http.MethodGet, "/api/v1/admin/hubs", consoleToken(t, "ops@doormile.com", 1, 0), "")
|
||||
if !strings.Contains(body, "Coimbatore Neptune Hub") || !strings.Contains(body, "Chennai Guindy Hub") {
|
||||
t.Fatalf("staff hub list = %s, want both cities", body)
|
||||
}
|
||||
}
|
||||
169
routes/routes_client_onboarding_pg_test.go
Normal file
169
routes/routes_client_onboarding_pg_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user