333 lines
8.2 KiB
Go
333 lines
8.2 KiB
Go
package scopes
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestExpandScopes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
required []Scope
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "nil returns nil",
|
|
required: nil,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "empty returns nil",
|
|
required: []Scope{},
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "repo scope returns just repo",
|
|
required: []Scope{Repo},
|
|
expected: []string{"repo"},
|
|
},
|
|
{
|
|
name: "public_repo also accepts repo (parent)",
|
|
required: []Scope{PublicRepo},
|
|
expected: []string{"public_repo", "repo"},
|
|
},
|
|
{
|
|
name: "security_events also accepts repo (parent)",
|
|
required: []Scope{SecurityEvents},
|
|
expected: []string{"repo", "security_events"},
|
|
},
|
|
{
|
|
name: "read:org also accepts write:org and admin:org (parents)",
|
|
required: []Scope{ReadOrg},
|
|
expected: []string{"admin:org", "read:org", "write:org"},
|
|
},
|
|
{
|
|
name: "write:org also accepts admin:org (parent)",
|
|
required: []Scope{WriteOrg},
|
|
expected: []string{"admin:org", "write:org"},
|
|
},
|
|
{
|
|
name: "admin:org returns just admin:org (no parent)",
|
|
required: []Scope{AdminOrg},
|
|
expected: []string{"admin:org"},
|
|
},
|
|
{
|
|
name: "read:project also accepts project (parent)",
|
|
required: []Scope{ReadProject},
|
|
expected: []string{"project", "read:project"},
|
|
},
|
|
{
|
|
name: "project returns just project (no parent)",
|
|
required: []Scope{Project},
|
|
expected: []string{"project"},
|
|
},
|
|
{
|
|
name: "gist returns just gist (no parent)",
|
|
required: []Scope{Gist},
|
|
expected: []string{"gist"},
|
|
},
|
|
{
|
|
name: "notifications returns just notifications (no parent)",
|
|
required: []Scope{Notifications},
|
|
expected: []string{"notifications"},
|
|
},
|
|
{
|
|
name: "read:packages also accepts write:packages (parent)",
|
|
required: []Scope{ReadPackages},
|
|
expected: []string{"read:packages", "write:packages"},
|
|
},
|
|
{
|
|
name: "read:user also accepts user (parent)",
|
|
required: []Scope{ReadUser},
|
|
expected: []string{"read:user", "user"},
|
|
},
|
|
{
|
|
name: "multiple scopes combine correctly",
|
|
required: []Scope{PublicRepo, ReadOrg},
|
|
expected: []string{"admin:org", "public_repo", "read:org", "repo", "write:org"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ExpandScopes(tt.required...)
|
|
|
|
// Sort both for consistent comparison
|
|
if result != nil {
|
|
sort.Strings(result)
|
|
}
|
|
if tt.expected != nil {
|
|
sort.Strings(tt.expected)
|
|
}
|
|
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestToStringSlice(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scopes []Scope
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "empty returns empty",
|
|
scopes: []Scope{},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "single scope",
|
|
scopes: []Scope{Repo},
|
|
expected: []string{"repo"},
|
|
},
|
|
{
|
|
name: "multiple scopes",
|
|
scopes: []Scope{Repo, Gist, ReadOrg},
|
|
expected: []string{"repo", "gist", "read:org"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ToStringSlice(tt.scopes...)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopeHierarchy(t *testing.T) {
|
|
// Verify the hierarchy is correctly defined
|
|
assert.Contains(t, ScopeHierarchy[Repo], PublicRepo)
|
|
assert.Contains(t, ScopeHierarchy[Repo], SecurityEvents)
|
|
assert.Contains(t, ScopeHierarchy[AdminOrg], WriteOrg)
|
|
assert.Contains(t, ScopeHierarchy[AdminOrg], ReadOrg)
|
|
assert.Contains(t, ScopeHierarchy[WriteOrg], ReadOrg)
|
|
assert.Contains(t, ScopeHierarchy[Project], ReadProject)
|
|
assert.Contains(t, ScopeHierarchy[WritePackages], ReadPackages)
|
|
assert.Contains(t, ScopeHierarchy[User], ReadUser)
|
|
assert.Contains(t, ScopeHierarchy[User], UserEmail)
|
|
}
|
|
|
|
func TestExpandScopeSet(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scopes []string
|
|
expected map[string]bool
|
|
}{
|
|
{
|
|
name: "empty scopes",
|
|
scopes: []string{},
|
|
expected: map[string]bool{},
|
|
},
|
|
{
|
|
name: "repo expands to include public_repo and security_events",
|
|
scopes: []string{"repo"},
|
|
expected: map[string]bool{
|
|
"repo": true,
|
|
"public_repo": true,
|
|
"security_events": true,
|
|
},
|
|
},
|
|
{
|
|
name: "admin:org expands to include write:org and read:org",
|
|
scopes: []string{"admin:org"},
|
|
expected: map[string]bool{
|
|
"admin:org": true,
|
|
"write:org": true,
|
|
"read:org": true,
|
|
},
|
|
},
|
|
{
|
|
name: "write:org expands to include read:org",
|
|
scopes: []string{"write:org"},
|
|
expected: map[string]bool{
|
|
"write:org": true,
|
|
"read:org": true,
|
|
},
|
|
},
|
|
{
|
|
name: "user expands to include read:user and user:email",
|
|
scopes: []string{"user"},
|
|
expected: map[string]bool{
|
|
"user": true,
|
|
"read:user": true,
|
|
"user:email": true,
|
|
},
|
|
},
|
|
{
|
|
name: "scope without children stays as-is",
|
|
scopes: []string{"gist"},
|
|
expected: map[string]bool{
|
|
"gist": true,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple scopes combine correctly",
|
|
scopes: []string{"repo", "gist"},
|
|
expected: map[string]bool{
|
|
"repo": true,
|
|
"public_repo": true,
|
|
"security_events": true,
|
|
"gist": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := expandScopeSet(tt.scopes)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasRequiredScopes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokenScopes []string
|
|
acceptedScopes []string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "no accepted scopes - always allowed",
|
|
tokenScopes: []string{},
|
|
acceptedScopes: []string{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "nil accepted scopes - always allowed",
|
|
tokenScopes: []string{"repo"},
|
|
acceptedScopes: nil,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token has exact required scope",
|
|
tokenScopes: []string{"repo"},
|
|
acceptedScopes: []string{"repo"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token has parent scope that grants access",
|
|
tokenScopes: []string{"repo"},
|
|
acceptedScopes: []string{"public_repo"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token has parent scope for security_events",
|
|
tokenScopes: []string{"repo"},
|
|
acceptedScopes: []string{"security_events"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token has admin:org which grants read:org",
|
|
tokenScopes: []string{"admin:org"},
|
|
acceptedScopes: []string{"read:org"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token has write:org which grants read:org",
|
|
tokenScopes: []string{"write:org"},
|
|
acceptedScopes: []string{"read:org"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "token missing required scope",
|
|
tokenScopes: []string{"gist"},
|
|
acceptedScopes: []string{"repo"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "token has child but not parent - fails",
|
|
tokenScopes: []string{"public_repo"},
|
|
acceptedScopes: []string{"repo"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple token scopes - one matches",
|
|
tokenScopes: []string{"gist", "repo"},
|
|
acceptedScopes: []string{"public_repo"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple accepted scopes - token has one",
|
|
tokenScopes: []string{"repo"},
|
|
acceptedScopes: []string{"repo", "admin:org"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "empty token scopes - fails when scopes required",
|
|
tokenScopes: []string{},
|
|
acceptedScopes: []string{"repo"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "user scope grants read:user",
|
|
tokenScopes: []string{"user"},
|
|
acceptedScopes: []string{"read:user"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "user scope grants user:email",
|
|
tokenScopes: []string{"user"},
|
|
acceptedScopes: []string{"user:email"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "write:packages grants read:packages",
|
|
tokenScopes: []string{"write:packages"},
|
|
acceptedScopes: []string{"read:packages"},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := HasRequiredScopes(tt.tokenScopes, tt.acceptedScopes)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|