package auth import ( "context" "testing" ) func TestCloudRoleFromGroupsRadarVocabulary(t *testing.T) { cases := []struct { name string groups []string want CloudRole }{ {"radar canonical", []string{"radar:member", "radar:org:o1", "radar:user:u1"}, RoleMember}, {"dual-emit picks tier once", []string{"radar:viewer", "radar:org:o1", "cloud:viewer", "cloud:org:o1"}, RoleViewer}, {"legacy cloud only (old hub)", []string{"cloud:owner", "cloud:org:o1"}, RoleOwner}, {"idp groups are not tiers", []string{"radar:idp:owner", "radar:idp:member"}, RoleNone}, {"highest rank wins across vocabularies", []string{"cloud:viewer", "radar:owner"}, RoleOwner}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := CloudRoleFromGroups(tc.groups); got != tc.want { t.Errorf("CloudRoleFromGroups(%v) = %q, want %q", tc.groups, got, tc.want) } }) } } func TestCloudRoleFromGroups(t *testing.T) { cases := []struct { name string groups []string want CloudRole }{ {"empty groups", nil, RoleNone}, {"no Radar prefix", []string{"developers", "sre"}, RoleNone}, {"viewer", []string{"radar:viewer"}, RoleViewer}, {"member", []string{"radar:member"}, RoleMember}, {"owner", []string{"radar:owner"}, RoleOwner}, {"unknown Radar tier", []string{"radar:superuser"}, RoleNone}, {"viewer + unrelated group", []string{"radar:viewer", "team:platform"}, RoleViewer}, // Cloud injects multiple radar:* groups (radar:, radar:org:, // radar:user:). Only the tier should drive the role. {"multiple Radar groups", []string{"radar:viewer", "radar:org:abc", "radar:user:xyz"}, RoleViewer}, // If two tiers are present (shouldn't happen in practice), prefer // the highest. Defensive: a header-stuffing attempt to downgrade // shouldn't succeed by adding a lower-tier alongside. {"two tiers — pick highest", []string{"radar:viewer", "radar:owner"}, RoleOwner}, {"member + viewer", []string{"radar:member", "radar:viewer"}, RoleMember}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := CloudRoleFromGroups(tc.groups) if got != tc.want { t.Errorf("CloudRoleFromGroups(%v) = %q, want %q", tc.groups, got, tc.want) } }) } } func TestCloudRole_AtLeast(t *testing.T) { cases := []struct { role CloudRole min CloudRole want bool }{ // RoleNone bypasses — non-Cloud deploys must not be gated. {RoleNone, RoleViewer, true}, {RoleNone, RoleMember, true}, {RoleNone, RoleOwner, true}, {RoleViewer, RoleViewer, true}, {RoleViewer, RoleMember, false}, {RoleViewer, RoleOwner, false}, {RoleMember, RoleViewer, true}, {RoleMember, RoleMember, true}, {RoleMember, RoleOwner, false}, {RoleOwner, RoleViewer, true}, {RoleOwner, RoleMember, true}, {RoleOwner, RoleOwner, true}, } for _, tc := range cases { got := tc.role.AtLeast(tc.min) if got != tc.want { t.Errorf("CloudRole(%q).AtLeast(%q) = %v, want %v", tc.role, tc.min, got, tc.want) } } } func TestCloudRoleFromContext(t *testing.T) { t.Run("no user in context", func(t *testing.T) { if got := CloudRoleFromContext(context.Background()); got != RoleNone { t.Errorf("CloudRoleFromContext(empty) = %q, want RoleNone", got) } }) t.Run("user with Cloud groups", func(t *testing.T) { ctx := ContextWithUser(context.Background(), &User{ Username: "alice", Groups: []string{"radar:member", "radar:org:abc"}, }) if got := CloudRoleFromContext(ctx); got != RoleMember { t.Errorf("CloudRoleFromContext = %q, want member", got) } }) t.Run("user without Cloud groups (OSS proxy/oidc)", func(t *testing.T) { ctx := ContextWithUser(context.Background(), &User{ Username: "bob", Groups: []string{"sre", "platform-team"}, }) if got := CloudRoleFromContext(ctx); got != RoleNone { t.Errorf("CloudRoleFromContext = %q, want RoleNone (no radar: prefix)", got) } }) }