Files
doormile_backend/routes/routes_client_onboarding_test.go

106 lines
3.6 KiB
Go

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)
}
}
}
}