package routes_test import ( "net/http" "strings" "testing" "doormile/config" "doormile/routes" "doormile/utils" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/recover" ) // Client onboarding's gate, over real HTTP. Only the configured owner login — // as Doormile staff with roleid 1 — reaches the handler; everyone else is // refused in middleware, before any database access. const onboardingOwner = "admin@doormile.com" var onboardingRoutes = []struct{ method, path, body string }{ {http.MethodPost, "/api/v1/admin/clients/onboard", `{"companyname":"Acme"}`}, {http.MethodGet, "/api/v1/admin/clients/onboarded", ""}, {http.MethodGet, "/api/v1/admin/clients/cities", ""}, {http.MethodPut, "/api/v1/admin/clients/5", `{"status":"Inactive"}`}, {http.MethodDelete, "/api/v1/admin/clients/5", ""}, } func onboardingApp() *fiber.App { app := fiber.New() app.Use(recover.New()) routes.RegisterRoutes(app, &config.Config{JWTSecret: jwtSecret, ClientOnboardingOwners: []string{onboardingOwner}}) return app } func consoleToken(t *testing.T, email string, roleID, tenantID int) string { t.Helper() tok, err := utils.GenerateToken(1, email, roleID, tenantID, 1, jwtSecret) if err != nil { t.Fatalf("mint token: %v", err) } return tok } func TestClientOnboardingNeedsALogin(t *testing.T) { app := onboardingApp() for _, r := range onboardingRoutes { if code, _ := do(t, app, r.method, r.path, "", r.body); code != http.StatusUnauthorized { t.Errorf("%s %s with no token = %d, want 401", r.method, r.path, code) } } } func TestClientOnboardingRefusesEveryoneButTheOwner(t *testing.T) { app := onboardingApp() cases := []struct { name string token string }{ {"another Doormile admin", consoleToken(t, "suriya@doormile.com", 1, 0)}, {"owner email as a manager", consoleToken(t, onboardingOwner, 3, 0)}, {"owner email as an executive", consoleToken(t, onboardingOwner, 4, 0)}, {"owner email on a client tenant", consoleToken(t, onboardingOwner, 1, 7)}, {"a client login", consoleToken(t, "ops@acme.example", 3, 7)}, {"a miler", consoleToken(t, onboardingOwner, 5, 0)}, {"a customer", consoleToken(t, onboardingOwner, 9, 0)}, } for _, c := range cases { for _, r := range onboardingRoutes { code, body := do(t, app, r.method, r.path, c.token, r.body) if code != http.StatusForbidden { t.Errorf("%s: %s %s = %d, want 403", c.name, r.method, r.path, code) } if strings.Contains(body, onboardingOwner) { t.Errorf("%s: the refusal leaked the owner email: %s", c.name, body) } } } } // With no owners configured, nobody gets in — not even admin@doormile.com. func TestClientOnboardingFailsClosedWithoutOwners(t *testing.T) { app := newApp() // config without ClientOnboardingOwners for _, r := range onboardingRoutes { if code, _ := do(t, app, r.method, r.path, consoleToken(t, onboardingOwner, 1, 0), r.body); code != http.StatusForbidden { t.Errorf("%s %s with no owners configured = %d, want 403", r.method, r.path, code) } } } // The owner passes every gate. There is no database here, so the handler's // first query panics and recover answers 500 — proof that the route exists // and nothing in front of it refused. Email matching ignores case. func TestClientOnboardingOwnerPassesTheGate(t *testing.T) { app := onboardingApp() for _, email := range []string{onboardingOwner, "Admin@Doormile.com"} { tok := consoleToken(t, email, 1, 0) for _, r := range onboardingRoutes { if code, _ := do(t, app, r.method, r.path, tok, r.body); code == 401 || code == 403 || code == 404 { t.Errorf("owner %s %s %s = %d; a gate refused or the route is missing", email, r.method, r.path, code) } } } }