An organization's members, limits and use are a panel of the people page, where they can also be changed, so the administrator's menu loses its Organizations entry and /org redirects there. Managers keep /org. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
146 lines
5.7 KiB
Go
146 lines
5.7 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// fakeOrgs: Acme (managed by u1) and Scania.
|
|
type fakeOrgs struct{ managers map[string]string } // org -> manager user id
|
|
|
|
func (f fakeOrgs) ManagesOrg(_ context.Context, user, org string) (bool, error) {
|
|
return f.managers[org] == user, nil
|
|
}
|
|
func (f fakeOrgs) ManagedOrgs(_ context.Context, user string) ([]store.Org, error) {
|
|
var out []store.Org
|
|
for _, o := range []store.Org{{ID: "org-a", Name: "Acme"}, {ID: "org-s", Name: "Scania"}} {
|
|
if f.managers[o.ID] == user {
|
|
out = append(out, o)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
func (f fakeOrgs) AllOrgs(context.Context) ([]store.Org, error) {
|
|
return []store.Org{{ID: "org-a", Name: "Acme"}, {ID: "org-s", Name: "Scania"}}, nil
|
|
}
|
|
func (f fakeOrgs) Tree(context.Context) ([]store.TreeOrg, error) {
|
|
return []store.TreeOrg{
|
|
{ID: "org-a", Name: "Acme", Teams: []store.TreeTeam{{ID: "t1", Name: "ml",
|
|
Projects: []store.TreeProject{{ID: "p1", Name: "training"}}}}},
|
|
{ID: "org-s", Name: "Scania"},
|
|
}, nil
|
|
}
|
|
func (f fakeOrgs) AllMemberships(context.Context) ([]store.Membership, error) {
|
|
return []store.Membership{
|
|
{UserID: "u1", Scope: store.ScopeOrg, ScopeID: "org-a", Role: store.RoleManager},
|
|
{UserID: "u2", Scope: store.ScopeProject, ScopeID: "p1", Role: store.RoleMember},
|
|
{UserID: "u3", Scope: store.ScopeOrg, ScopeID: "org-s", Role: store.RoleMember},
|
|
}, nil
|
|
}
|
|
func (f fakeOrgs) Users(context.Context, store.UserState) ([]store.User, error) {
|
|
return []store.User{{ID: "u1", Email: "boss@acme"}, {ID: "u2", Email: "dev@acme"}, {ID: "u3", Email: "lars@scania"}}, nil
|
|
}
|
|
func (f fakeOrgs) AllLimits(context.Context) (map[string]store.Limits, error) {
|
|
return map[string]store.Limits{}, nil
|
|
}
|
|
func (f fakeOrgs) UsageBucketsIn(context.Context, store.Grouping, string, time.Time, time.Duration) ([]store.UsageSeries, error) {
|
|
return nil, nil
|
|
}
|
|
func (f fakeOrgs) UsageByScope(context.Context, store.Scope, time.Time) ([]store.ScopeUsage, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func orgPage(t *testing.T, user store.User) *http.ServeMux {
|
|
t.Helper()
|
|
p := NewAccountPages(fakeAccounts{user: user}, nil, nil, "v0", slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
mux := http.NewServeMux()
|
|
p.RegisterOrgPages(mux, fakeOrgs{managers: map[string]string{"org-a": "u1"}}, nil)
|
|
return mux
|
|
}
|
|
|
|
func TestAnOrganizationsManagerSeesItAndNothingElse(t *testing.T) {
|
|
mux := orgPage(t, store.User{ID: "u1", Email: "boss@acme"})
|
|
w := mirrorGet(t, mux, "/org/org-a")
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("the manager was refused their own organization: %d", w.Code)
|
|
}
|
|
body := w.Body.String()
|
|
for _, want := range []string{"<h1>Acme</h1>", "dev@acme", "training", `href="/org"`} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the page lacks %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(body, "lars@scania") {
|
|
t.Error("another organization's member is on the page")
|
|
}
|
|
if w := mirrorGet(t, mux, "/org/org-s"); w.Code != http.StatusNotFound {
|
|
t.Errorf("a manager of Acme could open Scania: %d", w.Code)
|
|
}
|
|
if w := mirrorGet(t, mux, "/org"); w.Code != http.StatusSeeOther || w.Header().Get("Location") != "/org/org-a" {
|
|
t.Errorf("/org should lead to the organization they manage: %d %q", w.Code, w.Header().Get("Location"))
|
|
}
|
|
}
|
|
|
|
func TestSomebodyWhoManagesNothingGetsNoOrganizationPage(t *testing.T) {
|
|
mux := orgPage(t, store.User{ID: "u2", Email: "dev@acme"})
|
|
for _, path := range []string{"/org", "/org/org-a"} {
|
|
if w := mirrorGet(t, mux, path); w.Code != http.StatusNotFound {
|
|
t.Errorf("%s: %d, want 404", path, w.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnAdministratorSeesEveryOrganization(t *testing.T) {
|
|
mux := orgPage(t, store.User{ID: "admin", Email: "a@x", IsAdmin: true})
|
|
w := mirrorGet(t, mux, "/org/org-s")
|
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "lars@scania") {
|
|
t.Errorf("an administrator could not see Scania: %d", w.Code)
|
|
}
|
|
}
|
|
|
|
type noOrgs struct{ fakeOrgs }
|
|
|
|
func (noOrgs) AllOrgs(context.Context) ([]store.Org, error) { return nil, nil }
|
|
func (noOrgs) ManagedOrgs(context.Context, string) ([]store.Org, error) { return nil, nil }
|
|
|
|
func TestWithNoOrganizationsAnAdministratorIsToldSo(t *testing.T) {
|
|
p := NewAccountPages(fakeAccounts{user: store.User{ID: "a", IsAdmin: true}}, nil, nil, "v0",
|
|
slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
mux := http.NewServeMux()
|
|
p.RegisterOrgPages(mux, noOrgs{}, nil)
|
|
w := mirrorGet(t, mux, "/org")
|
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "There are none") {
|
|
t.Errorf("an empty cluster answered %d: %s", w.Code, w.Body.String())
|
|
}
|
|
// And the menu does not offer a page of nothing.
|
|
if strings.Contains(w.Body.String(), `href="/org"`) {
|
|
t.Error("the menu links to organizations when there are none")
|
|
}
|
|
}
|
|
|
|
func TestAnAdministratorIsSentToThePeoplePageForAnOrganization(t *testing.T) {
|
|
// Its members, limits and use are a panel there, beside the whole tree
|
|
// and changeable; /org stays for the managers who cannot see that page.
|
|
p := NewAccountPages(fakeAccounts{user: store.User{ID: "a", IsAdmin: true}}, nil, nil, "v0",
|
|
slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
mux := http.NewServeMux()
|
|
p.RegisterAdminUsers(mux, &fakeAdmin{})
|
|
p.RegisterOrgPages(mux, fakeOrgs{}, nil)
|
|
for path, to := range map[string]string{"/org": "/admin/users", "/org/org-s": "/admin/users?sel=org:org-s"} {
|
|
w := mirrorGet(t, mux, path)
|
|
if w.Code != http.StatusSeeOther || w.Header().Get("Location") != to {
|
|
t.Errorf("%s: %d to %q, want %s", path, w.Code, w.Header().Get("Location"), to)
|
|
}
|
|
}
|
|
if menu := menuRe.FindString(mirrorGet(t, mux, "/admin/users").Body.String()); strings.Contains(menu, `href="/org"`) {
|
|
t.Error("the administrator's menu still offers /org beside People")
|
|
}
|
|
}
|