package queryapi import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "queryorchestration/internal/cognitoauth" "queryorchestration/internal/usermanagement" "github.com/labstack/echo/v4" ) func TestAdminModeForRoles(t *testing.T) { tests := []struct { name string roles []string want adminTeamMode }{ {name: "super admin wins", roles: []string{"user_admin", "super_admin"}, want: adminTeamModeSuperAdmin}, {name: "client admin maps to user admin", roles: []string{"client_admin"}, want: adminTeamModeUserAdmin}, {name: "auditor without higher role", roles: []string{"auditor"}, want: adminTeamModeAuditor}, {name: "unrecognized", roles: []string{"client_user"}, want: adminTeamModeNone}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := adminModeForRoles(tt.roles); got != tt.want { t.Fatalf("adminModeForRoles(%v) = %s, want %s", tt.roles, got, tt.want) } }) } } func TestIsAAreteEmail(t *testing.T) { tests := []struct { email string want bool }{ {email: "person@aarete.com", want: true}, {email: "Person@AArete.com", want: true}, {email: "person@sub.aarete.com", want: false}, {email: "person@aarete.com.fake", want: false}, {email: "not-an-email", want: false}, } for _, tt := range tests { t.Run(tt.email, func(t *testing.T) { if got := isAAreteEmail(tt.email); got != tt.want { t.Fatalf("isAAreteEmail(%q) = %v, want %v", tt.email, got, tt.want) } }) } } func TestAdminTargetVisibilityAndMutation(t *testing.T) { clientRoles := []string{"client_user"} userAdminRoles := []string{"user_admin"} auditorRoles := []string{"auditor"} superAdminRoles := []string{"super_admin"} tests := []struct { name string mode adminTeamMode email string roles []string wantRead bool wantMutate bool }{ {name: "user admin can read and mutate client user", mode: adminTeamModeUserAdmin, email: "client@example.com", roles: clientRoles, wantRead: true, wantMutate: true}, {name: "user admin can read and mutate user admin", mode: adminTeamModeUserAdmin, email: "admin@example.com", roles: userAdminRoles, wantRead: true, wantMutate: true}, {name: "user admin cannot read or mutate aarete domain", mode: adminTeamModeUserAdmin, email: "person@aarete.com", roles: clientRoles, wantRead: false, wantMutate: false}, {name: "user admin cannot read or mutate auditor", mode: adminTeamModeUserAdmin, email: "auditor@example.com", roles: auditorRoles, wantRead: false, wantMutate: false}, {name: "user admin cannot read or mutate super admin", mode: adminTeamModeUserAdmin, email: "super@example.com", roles: superAdminRoles, wantRead: false, wantMutate: false}, {name: "auditor can read all", mode: adminTeamModeAuditor, email: "person@aarete.com", roles: superAdminRoles, wantRead: true, wantMutate: false}, {name: "super admin can read and mutate all", mode: adminTeamModeSuperAdmin, email: "person@aarete.com", roles: superAdminRoles, wantRead: true, wantMutate: true}, {name: "none cannot read or mutate", mode: adminTeamModeNone, email: "client@example.com", roles: clientRoles, wantRead: false, wantMutate: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := canAdminModeReadTarget(tt.mode, tt.email, tt.roles); got != tt.wantRead { t.Fatalf("canAdminModeReadTarget() = %v, want %v", got, tt.wantRead) } if got := canAdminModeMutateTarget(tt.mode, tt.email, tt.roles); got != tt.wantMutate { t.Fatalf("canAdminModeMutateTarget() = %v, want %v", got, tt.wantMutate) } }) } } func TestAdminCreatePermissions(t *testing.T) { tests := []struct { name string mode adminTeamMode email string roles []string want bool }{ {name: "super admin can create all roles", mode: adminTeamModeSuperAdmin, email: "person@aarete.com", roles: []string{"super_admin"}, want: true}, {name: "user admin can create client user", mode: adminTeamModeUserAdmin, email: "client@example.com", roles: []string{"client_user"}, want: true}, {name: "user admin can create user admin", mode: adminTeamModeUserAdmin, email: "admin@example.com", roles: []string{"client_user", "user_admin"}, want: true}, {name: "user admin cannot create aarete domain", mode: adminTeamModeUserAdmin, email: "person@aarete.com", roles: []string{"client_user"}, want: false}, {name: "user admin cannot create auditor", mode: adminTeamModeUserAdmin, email: "auditor@example.com", roles: []string{"auditor"}, want: false}, {name: "auditor cannot create", mode: adminTeamModeAuditor, email: "client@example.com", roles: []string{"client_user"}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := canAdminModeCreateUser(tt.mode, tt.email, tt.roles); got != tt.want { t.Fatalf("canAdminModeCreateUser() = %v, want %v", got, tt.want) } }) } } func TestUserAdminCanAssignRoles(t *testing.T) { tests := []struct { name string roles []string want bool }{ {name: "client user", roles: []string{"client_user"}, want: true}, {name: "user admin", roles: []string{"user_admin"}, want: true}, {name: "client user and user admin", roles: []string{"client_user", "user_admin"}, want: true}, {name: "auditor rejected", roles: []string{"auditor"}, want: false}, {name: "super admin rejected", roles: []string{"super_admin"}, want: false}, {name: "unknown role rejected", roles: []string{"custom_role"}, want: false}, {name: "mixed allowed and protected rejected", roles: []string{"client_user", "auditor"}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := userAdminCanAssignRoles(tt.roles); got != tt.want { t.Fatalf("userAdminCanAssignRoles(%v) = %v, want %v", tt.roles, got, tt.want) } }) } } func TestCanAdminModeAssignRequestedRoles(t *testing.T) { auditorRoles := []string{"auditor"} clientRoles := []string{"client_user"} tests := []struct { name string mode adminTeamMode roles *[]string want bool }{ {name: "nil roles allowed", mode: adminTeamModeUserAdmin, roles: nil, want: true}, {name: "user admin can assign client role", mode: adminTeamModeUserAdmin, roles: &clientRoles, want: true}, {name: "user admin cannot assign auditor role", mode: adminTeamModeUserAdmin, roles: &auditorRoles, want: false}, {name: "super admin can assign auditor role", mode: adminTeamModeSuperAdmin, roles: &auditorRoles, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := canAdminModeAssignRequestedRoles(tt.mode, tt.roles); got != tt.want { t.Fatalf("canAdminModeAssignRequestedRoles() = %v, want %v", got, tt.want) } }) } } func TestGetAdminTeamMode(t *testing.T) { server := newAdminListPermitServer(t) defer server.Close() ctrl := NewControllers(&Services{}, &mockAuthConfig{}) permitConfig := &usermanagement.PermitConfig{ APIKey: "test-api-key", BaseURL: server.URL, } t.Run("disabled auth acts as super admin", func(t *testing.T) { t.Setenv("DISABLE_AUTH", "true") ctx, _ := newAdminAccessTestContext(http.MethodGet) if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeSuperAdmin { t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeSuperAdmin) } }) t.Run("permit subject roles determine mode", func(t *testing.T) { t.Setenv("DISABLE_AUTH", "") ctx, _ := newAdminAccessTestContext(http.MethodGet) ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeUserAdmin { t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeUserAdmin) } }) t.Run("context groups are used when subject is unavailable", func(t *testing.T) { t.Setenv("DISABLE_AUTH", "") ctx, _ := newAdminAccessTestContext(http.MethodGet) ctx.Set("user_info", cognitoauth.UserInfo{Groups: []string{"auditor"}}) if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeAuditor { t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeAuditor) } }) t.Run("no auth context has no admin mode", func(t *testing.T) { t.Setenv("DISABLE_AUTH", "") ctx, _ := newAdminAccessTestContext(http.MethodGet) if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeNone { t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeNone) } }) } func TestGetAdminTargetAccess(t *testing.T) { server := newAdminListPermitServer(t) defer server.Close() t.Setenv("DISABLE_AUTH", "false") t.Setenv("PERMIT_IO_BASE_URL", server.URL) ctrl := NewControllers(&Services{}, &mockAuthConfig{}) t.Run("loads target roles and caller mode", func(t *testing.T) { ctx, _ := newAdminAccessTestContext(http.MethodGet) ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) cognitoUser := &usermanagement.CognitoUserResponse{ SubjectID: "client-sub", Email: "client@example.com", } access := ctrl.getAdminTargetAccess(ctx, UserEmail(cognitoUser.Email), cognitoUser) if access.mode != adminTeamModeUserAdmin { t.Fatalf("getAdminTargetAccess().mode = %s, want %s", access.mode, adminTeamModeUserAdmin) } if !access.roleLookupOK { t.Fatalf("getAdminTargetAccess().roleLookupOK = false, want true") } if len(access.roles) != 1 || access.roles[0] != "client_user" { t.Fatalf("getAdminTargetAccess().roles = %v, want [client_user]", access.roles) } if !access.canRead(cognitoUser.Email) { t.Fatalf("adminTargetAccess.canRead() = false, want true") } if !access.canMutate(cognitoUser.Email) { t.Fatalf("adminTargetAccess.canMutate() = false, want true") } }) t.Run("user admin fail closes when target role lookup fails", func(t *testing.T) { ctx, _ := newAdminAccessTestContext(http.MethodPatch) ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) cognitoUser := &usermanagement.CognitoUserResponse{ SubjectID: "role-error-sub", Email: "client@example.com", } access := ctrl.getAdminTargetAccess(ctx, UserEmail(cognitoUser.Email), cognitoUser) if access.mode != adminTeamModeUserAdmin { t.Fatalf("getAdminTargetAccess().mode = %s, want %s", access.mode, adminTeamModeUserAdmin) } if access.roleLookupOK { t.Fatalf("getAdminTargetAccess().roleLookupOK = true, want false") } if access.canRead(cognitoUser.Email) { t.Fatalf("adminTargetAccess.canRead() = true, want false") } if access.canMutate(cognitoUser.Email) { t.Fatalf("adminTargetAccess.canMutate() = true, want false") } }) } func TestAdminUserErrorResponses(t *testing.T) { ctx, rec := newAdminAccessTestContext(http.MethodPatch) if err := adminUserForbidden(ctx); err != nil { t.Fatalf("adminUserForbidden() error = %v", err) } if rec.Code != http.StatusForbidden { t.Fatalf("adminUserForbidden() status = %d, want %d", rec.Code, http.StatusForbidden) } if !strings.Contains(rec.Body.String(), "Insufficient permissions") { t.Fatalf("adminUserForbidden() body = %q, want permissions message", rec.Body.String()) } ctx, rec = newAdminAccessTestContext(http.MethodGet) if err := adminUserNotFound(ctx, "hidden@example.com"); err != nil { t.Fatalf("adminUserNotFound() error = %v", err) } if rec.Code != http.StatusNotFound { t.Fatalf("adminUserNotFound() status = %d, want %d", rec.Code, http.StatusNotFound) } if !strings.Contains(rec.Body.String(), "User not found: hidden@example.com") { t.Fatalf("adminUserNotFound() body = %q, want not found message", rec.Body.String()) } } func TestAdminUserListPaginationAndPage(t *testing.T) { pageInput := int32(2) pageSizeInput := int32(2) page, pageSize := adminUserListPagination(ListAdminUsersParams{ Page: &pageInput, PageSize: &pageSizeInput, }) if page != 2 || pageSize != 2 { t.Fatalf("adminUserListPagination() = (%d, %d), want (2, 2)", page, pageSize) } tooLargePageSize := int32(100) _, defaultedPageSize := adminUserListPagination(ListAdminUsersParams{PageSize: &tooLargePageSize}) if defaultedPageSize != 50 { t.Fatalf("adminUserListPagination() pageSize = %d, want default 50", defaultedPageSize) } users := []AdminUserDetails{ {CognitoSubjectId: "sub-1"}, {CognitoSubjectId: "sub-2"}, {CognitoSubjectId: "sub-3"}, } pageUsers, hasMore := paginateAdminUsers(users, 1, 2) if len(pageUsers) != 2 || !hasMore { t.Fatalf("paginateAdminUsers() = %d users, hasMore %v; want 2 users and hasMore true", len(pageUsers), hasMore) } pageUsers, hasMore = paginateAdminUsers(users, 2, 2) if len(pageUsers) != 1 || hasMore { t.Fatalf("paginateAdminUsers(page 2) = %d users, hasMore %v; want 1 user and hasMore false", len(pageUsers), hasMore) } } func TestCollectAdminCognitoUsersStopsAtUserCap(t *testing.T) { nextToken := "next" _, err := collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { users := make([]usermanagement.CognitoUserResponse, pageSize+1) for i := range users { users[i] = usermanagement.CognitoUserResponse{SubjectID: "sub"} } return &usermanagement.ListCognitoUsersResult{Users: users, PaginationKey: &nextToken}, nil }) if err == nil || !strings.Contains(err.Error(), "exceeded 6000 users") { t.Fatalf("collectAdminCognitoUsers() error = %v, want user cap error", err) } } func TestCollectAdminCognitoUsersStopsAtPageCap(t *testing.T) { nextToken := "next" _, err := collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { return &usermanagement.ListCognitoUsersResult{ Users: []usermanagement.CognitoUserResponse{{SubjectID: "sub"}}, PaginationKey: &nextToken, }, nil }) if err == nil || !strings.Contains(err.Error(), "exceeded 100 pages") { t.Fatalf("collectAdminCognitoUsers() error = %v, want page cap error", err) } } func TestVisibleAdminUsersAppliesDEVOPS722Visibility(t *testing.T) { users := []usermanagement.CognitoUserResponse{ {SubjectID: "client-sub", Email: "client@example.com", FirstName: "Client", LastName: "User", Status: "CONFIRMED", Enabled: true}, {SubjectID: "user-admin-sub", Email: "admin@example.com", FirstName: "User", LastName: "Admin", Status: "CONFIRMED", Enabled: true}, {SubjectID: "aarete-sub", Email: "person@aarete.com", FirstName: "AArete", LastName: "User", Status: "CONFIRMED", Enabled: true}, {SubjectID: "auditor-sub", Email: "auditor@example.com", FirstName: "Audit", LastName: "User", Status: "CONFIRMED", Enabled: true}, {SubjectID: "super-sub", Email: "super@example.com", FirstName: "Super", LastName: "User", Status: "CONFIRMED", Enabled: true}, {SubjectID: "missing-role-sub", Email: "missing-role@example.com", FirstName: "Missing", LastName: "Role", Status: "CONFIRMED", Enabled: true}, } rolesBySubject := map[string][]string{ "client-sub": {"client_user"}, "user-admin-sub": {"user_admin"}, "aarete-sub": {"client_user"}, "auditor-sub": {"auditor"}, "super-sub": {"super_admin"}, } ctrl := NewControllers(&Services{}, &mockAuthConfig{}) userAdminVisible := ctrl.visibleAdminUsers(users, adminTeamModeUserAdmin, rolesBySubject) assertVisibleSubjects(t, userAdminVisible, []string{"client-sub", "user-admin-sub"}) auditorVisible := ctrl.visibleAdminUsers(users, adminTeamModeAuditor, rolesBySubject) assertVisibleSubjects(t, auditorVisible, []string{"client-sub", "user-admin-sub", "aarete-sub", "auditor-sub", "super-sub", "missing-role-sub"}) superAdminVisible := ctrl.visibleAdminUsers(users, adminTeamModeSuperAdmin, rolesBySubject) assertVisibleSubjects(t, superAdminVisible, []string{"client-sub", "user-admin-sub", "aarete-sub", "auditor-sub", "super-sub", "missing-role-sub"}) } func TestAdminRoleAssignmentsByUserGroupsRoles(t *testing.T) { got := adminRoleAssignmentsByUser([]usermanagement.RoleAssignment{ {User: "sub-1", Role: "client_user", Tenant: "default"}, {User: "sub-1", Role: "user_admin", Tenant: "default"}, {User: "sub-2", Role: "auditor", Tenant: "default"}, {User: "", Role: "super_admin", Tenant: "default"}, {User: "sub-3", Role: "", Tenant: "default"}, }) assertVisibleRoleMap(t, got, map[string][]string{ "sub-1": {"client_user", "user_admin"}, "sub-2": {"auditor"}, }) } func TestAdminUserDetailsRolesMarshalAsEmptyArray(t *testing.T) { detail := adminUserDetailsFromCognito(usermanagement.CognitoUserResponse{ SubjectID: "sub-without-roles", Email: "client@example.com", }, nil) body, err := json.Marshal(detail) if err != nil { t.Fatalf("marshal admin user details: %v", err) } if strings.Contains(string(body), `"roles":null`) { t.Fatalf("admin user details marshaled roles as null: %s", body) } if !strings.Contains(string(body), `"roles":[]`) { t.Fatalf("admin user details missing empty roles array: %s", body) } } func TestUpdateUserRolesInPermitEmptyArrayClearsRoles(t *testing.T) { var deletedRoles []string mux := http.NewServeMux() mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(map[string]string{ "organization_id": "test-org", "project_id": "test-project", "environment_id": "test-env", }); err != nil { t.Fatalf("encode scope response: %v", err) } }) mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.Method { case http.MethodGet: if err := json.NewEncoder(w).Encode([]map[string]string{ {"role": "client_user", "user": "target-sub", "tenant": "default"}, {"role": "user_admin", "user": "target-sub", "tenant": "default"}, }); err != nil { t.Fatalf("encode role assignments: %v", err) } case http.MethodDelete: var assignment usermanagement.RoleAssignment if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { t.Fatalf("decode delete role assignment: %v", err) } deletedRoles = append(deletedRoles, assignment.Role) w.WriteHeader(http.StatusNoContent) default: t.Fatalf("unexpected method %s", r.Method) } }) roleServer := httptest.NewServer(mux) defer roleServer.Close() ctrl := NewControllers(&Services{}, &mockAuthConfig{}) ctx, _ := newAdminAccessTestContext(http.MethodPatch) emptyRoles := []string{} ctrl.updateUserRolesInPermit( ctx, UserEmail("target@example.com"), AdminUserUpdate{Roles: &emptyRoles}, cognitoauth.UserInfo{Email: "admin@example.com"}, "target-sub", roleServer.Client(), &usermanagement.PermitConfig{APIKey: "test-api-key", BaseURL: roleServer.URL}, ) assertVisibleRoleMap(t, map[string][]string{"deleted": deletedRoles}, map[string][]string{ "deleted": {"client_user", "user_admin"}, }) } func TestUpdateUserRolesInPermitAddsAndRemovesRoles(t *testing.T) { var assignedRoles []string var deletedRoles []string mux := http.NewServeMux() mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(map[string]string{ "organization_id": "test-org", "project_id": "test-project", "environment_id": "test-env", }); err != nil { t.Fatalf("encode scope response: %v", err) } }) mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.Method { case http.MethodGet: if err := json.NewEncoder(w).Encode([]map[string]string{ {"role": "client_user", "user": "target-sub", "tenant": "default"}, {"role": "user_admin", "user": "target-sub", "tenant": "default"}, }); err != nil { t.Fatalf("encode role assignments: %v", err) } case http.MethodPost: var assignment usermanagement.RoleAssignment if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { t.Fatalf("decode assign role assignment: %v", err) } assignedRoles = append(assignedRoles, assignment.Role) w.WriteHeader(http.StatusCreated) case http.MethodDelete: var assignment usermanagement.RoleAssignment if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { t.Fatalf("decode delete role assignment: %v", err) } deletedRoles = append(deletedRoles, assignment.Role) w.WriteHeader(http.StatusNoContent) default: t.Fatalf("unexpected method %s", r.Method) } }) roleServer := httptest.NewServer(mux) defer roleServer.Close() ctrl := NewControllers(&Services{}, &mockAuthConfig{}) ctx, _ := newAdminAccessTestContext(http.MethodPatch) requestedRoles := []string{"client_user", "auditor"} ctrl.updateUserRolesInPermit( ctx, UserEmail("target@example.com"), AdminUserUpdate{Roles: &requestedRoles}, cognitoauth.UserInfo{Email: "admin@example.com"}, "target-sub", roleServer.Client(), &usermanagement.PermitConfig{APIKey: "test-api-key", BaseURL: roleServer.URL}, ) assertVisibleRoleMap(t, map[string][]string{ "assigned": assignedRoles, "deleted": deletedRoles, }, map[string][]string{ "assigned": {"auditor"}, "deleted": {"user_admin"}, }) } func newAdminListPermitServer(t *testing.T) *httptest.Server { t.Helper() mux := http.NewServeMux() mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(map[string]string{ "organization_id": "test-org", "project_id": "test-project", "environment_id": "test-env", }); err != nil { t.Fatalf("encode scope response: %v", err) } }) mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { user := r.URL.Query().Get("user") if user == "role-error-sub" { http.Error(w, "permit unavailable", http.StatusInternalServerError) return } roleByUser := map[string]string{ "client-sub": "client_user", "user-admin-sub": "user_admin", "aarete-sub": "client_user", "auditor-sub": "auditor", "super-sub": "super_admin", } role := roleByUser[user] assignments := []map[string]string{} if role != "" { assignments = append(assignments, map[string]string{"role": role, "user": user, "tenant": "default"}) } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(assignments); err != nil { t.Fatalf("encode role assignments: %v", err) } }) return httptest.NewServer(mux) } func newAdminAccessTestContext(method string) (echo.Context, *httptest.ResponseRecorder) { e := echo.New() req := httptest.NewRequest(method, "/admin/users", nil) rec := httptest.NewRecorder() return e.NewContext(req, rec), rec } func assertVisibleSubjects(t *testing.T, users []AdminUserDetails, want []string) { t.Helper() got := make([]string, 0, len(users)) for _, user := range users { got = append(got, user.CognitoSubjectId) } if len(got) != len(want) { t.Fatalf("visible subjects = %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("visible subjects = %v, want %v", got, want) } } } func assertVisibleRoleMap(t *testing.T, got map[string][]string, want map[string][]string) { t.Helper() if len(got) != len(want) { t.Fatalf("role map = %v, want %v", got, want) } for subject, wantRoles := range want { gotRoles, ok := got[subject] if !ok { t.Fatalf("role map missing subject %q: %v", subject, got) } if len(gotRoles) != len(wantRoles) { t.Fatalf("role map[%q] = %v, want %v", subject, gotRoles, wantRoles) } for i := range wantRoles { if gotRoles[i] != wantRoles[i] { t.Fatalf("role map[%q] = %v, want %v", subject, gotRoles, wantRoles) } } } }