updates on the onboardings and hubs patches as well
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user