106 lines
3.6 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
}
|