mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2025-12-23 06:15:47 +00:00
834 lines
23 KiB
Go
834 lines
23 KiB
Go
// Copyright 2024-2025 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package githubclient
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-github/v74/github"
|
|
"github.com/migueleliasweb/go-github-mock/src/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/client-go/util/cert"
|
|
|
|
"go.pinniped.dev/internal/net/phttp"
|
|
"go.pinniped.dev/internal/setutil"
|
|
"go.pinniped.dev/internal/testutil/tlsserver"
|
|
)
|
|
|
|
func TestNewGitHubClient(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("rejects nil http client", func(t *testing.T) {
|
|
_, err := NewGitHubClient(nil, "https://api.github.com/", "")
|
|
require.EqualError(t, err, "unable to build new github client: httpClient cannot be nil")
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
apiBaseURL string
|
|
token string
|
|
wantBaseURL string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "happy path with https://api.github.com/",
|
|
apiBaseURL: "https://api.github.com/",
|
|
token: "some-token",
|
|
wantBaseURL: "https://api.github.com/",
|
|
},
|
|
{
|
|
name: "adds trailing slash to path for https://api.github.com",
|
|
apiBaseURL: "https://api.github.com",
|
|
token: "other-token",
|
|
wantBaseURL: "https://api.github.com/",
|
|
},
|
|
{
|
|
name: "adds trailing slash to path for Enterprise URL https://fake.enterprise.tld/api/v3",
|
|
apiBaseURL: "https://fake.enterprise.tld/api/v3",
|
|
token: "some-enterprise-token",
|
|
wantBaseURL: "https://fake.enterprise.tld/api/v3/",
|
|
},
|
|
{
|
|
name: "rejects apiBaseURL without https:// scheme",
|
|
apiBaseURL: "scp://github.com",
|
|
token: "some-token",
|
|
wantErr: `unable to build new github client: apiBaseURL must use "https" protocol, found "scp" instead`,
|
|
},
|
|
{
|
|
name: "rejects apiBaseURL with empty scheme",
|
|
apiBaseURL: "github.com",
|
|
token: "some-token",
|
|
wantErr: `unable to build new github client: apiBaseURL must use "https" protocol, found "" instead`,
|
|
},
|
|
{
|
|
name: "rejects empty token",
|
|
apiBaseURL: "https://api.github.com/",
|
|
wantErr: "unable to build new github client: token cannot be empty string",
|
|
},
|
|
{
|
|
name: "returns errors from url.Parse",
|
|
apiBaseURL: "https:// example.com",
|
|
token: "some-token",
|
|
wantErr: `unable to build new github client: parse "https:// example.com": invalid character " " in host name`,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
called := false
|
|
testServer, testServerCA := tlsserver.TestServerIPv4(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Len(t, r.Header["Authorization"], 1)
|
|
require.Equal(t, "Bearer "+test.token, r.Header.Get("Authorization"))
|
|
called = true
|
|
}), nil)
|
|
|
|
t.Cleanup(func() {
|
|
require.True(t, (test.wantErr == "" && called) || (test.wantErr != "" && !called))
|
|
})
|
|
|
|
pool, err := cert.NewPoolFromBytes(testServerCA)
|
|
require.NoError(t, err)
|
|
|
|
httpClient := phttp.Default(pool)
|
|
|
|
actualI, err := NewGitHubClient(httpClient, test.apiBaseURL, test.token)
|
|
|
|
if test.wantErr != "" {
|
|
require.EqualError(t, err, test.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, actualI)
|
|
actual, ok := actualI.(*githubClient)
|
|
require.True(t, ok)
|
|
require.NotNil(t, actual.client.BaseURL)
|
|
require.Equal(t, test.wantBaseURL, actual.client.BaseURL.String())
|
|
|
|
// Force the githubClient's httpClient roundTrippers to run and add the Authorization header
|
|
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, testServer.URL, nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = actual.client.Client().Do(req) //nolint:bodyclose
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetUser(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
httpClient *http.Client
|
|
token string
|
|
ctx context.Context
|
|
wantErr string
|
|
wantUserInfo UserInfo
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUser,
|
|
github.User{
|
|
Login: github.Ptr("some-username"),
|
|
ID: github.Ptr[int64](12345678),
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantUserInfo: UserInfo{
|
|
Login: "some-username",
|
|
ID: "12345678",
|
|
},
|
|
},
|
|
{
|
|
name: "the token is added in the Authorization header",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUser,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Len(t, r.Header["Authorization"], 1)
|
|
require.Equal(t, "Bearer does-this-token-work", r.Header.Get("Authorization"))
|
|
_, err := w.Write([]byte(`{"login":"some-authenticated-username","id":999888}`))
|
|
require.NoError(t, err)
|
|
}),
|
|
),
|
|
),
|
|
token: "does-this-token-work",
|
|
wantUserInfo: UserInfo{
|
|
Login: "some-authenticated-username",
|
|
ID: "999888",
|
|
},
|
|
},
|
|
{
|
|
name: "handles missing login",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUser,
|
|
github.User{
|
|
ID: github.Ptr[int64](12345678),
|
|
},
|
|
),
|
|
),
|
|
token: "does-this-token-work",
|
|
wantErr: `error fetching authenticated user: the "login" attribute is missing`,
|
|
},
|
|
{
|
|
name: "handles missing ID",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUser,
|
|
github.User{
|
|
Login: github.Ptr("some-username"),
|
|
},
|
|
),
|
|
),
|
|
token: "does-this-token-work",
|
|
wantErr: `error fetching authenticated user: the "id" attribute is missing`,
|
|
},
|
|
{
|
|
name: "passes the context parameter into the API call",
|
|
token: "some-token",
|
|
httpClient: mock.NewMockedHTTPClient(),
|
|
ctx: func() context.Context {
|
|
canceledCtx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
return canceledCtx
|
|
}(),
|
|
wantErr: "error fetching authenticated user: context canceled",
|
|
},
|
|
{
|
|
name: "returns errors from the API",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUser,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mock.WriteError(
|
|
w,
|
|
http.StatusInternalServerError,
|
|
"internal server error from the server",
|
|
)
|
|
}),
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantErr: "error fetching authenticated user: GET {SERVER_URL}/user: 500 internal server error from the server []",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
githubClient := &githubClient{
|
|
client: github.NewClient(test.httpClient).WithAuthToken(test.token),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if test.ctx != nil {
|
|
ctx = test.ctx
|
|
}
|
|
|
|
actual, err := githubClient.GetUserInfo(ctx)
|
|
if test.wantErr != "" {
|
|
rt, ok := test.httpClient.Transport.(*mock.EnforceHostRoundTripper)
|
|
require.True(t, ok)
|
|
test.wantErr = strings.ReplaceAll(test.wantErr, "{SERVER_URL}", rt.Host)
|
|
require.EqualError(t, err, test.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, actual)
|
|
require.Equal(t, test.wantUserInfo, *actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOrgMembership(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
httpClient *http.Client
|
|
token string
|
|
ctx context.Context
|
|
wantErr string
|
|
wantOrgs []string
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserOrgs,
|
|
[]github.Organization{
|
|
{Login: github.Ptr("org1")},
|
|
{Login: github.Ptr("org2")},
|
|
{Login: github.Ptr("org3")},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantOrgs: []string{"org1", "org2", "org3"},
|
|
},
|
|
{
|
|
name: "happy path with pagination",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchPages(
|
|
mock.GetUserOrgs,
|
|
[]github.Organization{
|
|
{Login: github.Ptr("page1-org1")},
|
|
{Login: github.Ptr("page1-org2")},
|
|
{Login: github.Ptr("page1-org3")},
|
|
},
|
|
[]github.Organization{
|
|
{Login: github.Ptr("page2-org1")},
|
|
{Login: github.Ptr("page2-org2")},
|
|
{Login: github.Ptr("page2-org3")},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantOrgs: []string{"page1-org1", "page1-org2", "page1-org3", "page2-org1", "page2-org2", "page2-org3"},
|
|
},
|
|
{
|
|
name: "the token is added in the Authorization header",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUserOrgs,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Len(t, r.Header["Authorization"], 1)
|
|
require.Equal(t, "Bearer does-this-token-work", r.Header.Get("Authorization"))
|
|
_, err := w.Write([]byte(`[{"login":"some-org-to-which-the-authenticated-user-belongs"}]`))
|
|
require.NoError(t, err)
|
|
}),
|
|
),
|
|
),
|
|
token: "does-this-token-work",
|
|
wantOrgs: []string{"some-org-to-which-the-authenticated-user-belongs"},
|
|
},
|
|
{
|
|
name: "errors when a Login field is empty",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserOrgs,
|
|
[]github.Organization{
|
|
{Login: github.Ptr("page1-org1")},
|
|
{Login: nil},
|
|
{Login: github.Ptr("page1-org3")},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantErr: `error fetching organizations for authenticated user: one or more organizations is missing the "login" attribute`,
|
|
},
|
|
{
|
|
name: "passes the context parameter into the API call",
|
|
token: "some-token",
|
|
httpClient: mock.NewMockedHTTPClient(),
|
|
ctx: func() context.Context {
|
|
canceledCtx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
return canceledCtx
|
|
}(),
|
|
wantErr: "error fetching organizations for authenticated user: context canceled",
|
|
},
|
|
{
|
|
name: "returns errors from the API",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUserOrgs,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mock.WriteError(
|
|
w,
|
|
http.StatusFailedDependency,
|
|
"some random client error",
|
|
)
|
|
}),
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantErr: "error fetching organizations for authenticated user: GET {SERVER_URL}/user/orgs?per_page=100: 424 some random client error []",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
githubClient := &githubClient{
|
|
client: github.NewClient(test.httpClient).WithAuthToken(test.token),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if test.ctx != nil {
|
|
ctx = test.ctx
|
|
}
|
|
|
|
actual, err := githubClient.GetOrgMembership(ctx)
|
|
if test.wantErr != "" {
|
|
rt, ok := test.httpClient.Transport.(*mock.EnforceHostRoundTripper)
|
|
require.True(t, ok)
|
|
test.wantErr = strings.ReplaceAll(test.wantErr, "{SERVER_URL}", rt.Host)
|
|
require.EqualError(t, err, test.wantErr)
|
|
return
|
|
}
|
|
|
|
require.NotNil(t, actual)
|
|
require.ElementsMatch(t, test.wantOrgs, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTeamMembership(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
httpClient *http.Client
|
|
token string
|
|
ctx context.Context
|
|
allowedOrganizations *setutil.CaseInsensitiveSet
|
|
wantErr string
|
|
wantTeams []TeamInfo
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("orgAlpha-team1-name"),
|
|
Slug: github.Ptr("orgAlpha-team1-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("alpha"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("orgAlpha-team2-name"),
|
|
Slug: github.Ptr("orgAlpha-team2-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("alpha"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("orgAlpha-team3-name"),
|
|
Slug: github.Ptr("orgAlpha-team3-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("alpha"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("orgBeta-team1-name"),
|
|
Slug: github.Ptr("orgBeta-team1-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("beta"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
allowedOrganizations: setutil.NewCaseInsensitiveSet("alpha", "beta"),
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "orgAlpha-team1-name",
|
|
Slug: "orgAlpha-team1-slug",
|
|
Org: "alpha",
|
|
},
|
|
{
|
|
Name: "orgAlpha-team2-name",
|
|
Slug: "orgAlpha-team2-slug",
|
|
Org: "alpha",
|
|
},
|
|
{
|
|
Name: "orgAlpha-team3-name",
|
|
Slug: "orgAlpha-team3-slug",
|
|
Org: "alpha",
|
|
},
|
|
{
|
|
Name: "orgBeta-team1-name",
|
|
Slug: "orgBeta-team1-slug",
|
|
Org: "beta",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "filters by allowedOrganizations in a case-insensitive way, but preserves case as returned by GitHub API in the result",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team1-name"),
|
|
Slug: github.Ptr("team1-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("alPhA"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team2-name"),
|
|
Slug: github.Ptr("team2-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("bEtA"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team3-name"),
|
|
Slug: github.Ptr("team3-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("gAmmA"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
allowedOrganizations: setutil.NewCaseInsensitiveSet("ALPHA", "gamma"),
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "team1-name",
|
|
Slug: "team1-slug",
|
|
Org: "alPhA",
|
|
},
|
|
{
|
|
Name: "team3-name",
|
|
Slug: "team3-slug",
|
|
Org: "gAmmA",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "when allowedOrganizations is empty, return all teams",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team1-name"),
|
|
Slug: github.Ptr("team1-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("alpha"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team2-name"),
|
|
Slug: github.Ptr("team2-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("beta"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team3-name"),
|
|
Slug: github.Ptr("team3-slug"),
|
|
Parent: &github.Team{
|
|
Name: github.Ptr("delta-team-name"),
|
|
Slug: github.Ptr("delta-team-slug"),
|
|
Organization: nil, // the real GitHub API does not return Org on "Parent" team.
|
|
},
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("gamma"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "team1-name",
|
|
Slug: "team1-slug",
|
|
Org: "alpha",
|
|
},
|
|
{
|
|
Name: "team2-name",
|
|
Slug: "team2-slug",
|
|
Org: "beta",
|
|
},
|
|
{
|
|
Name: "delta-team-name",
|
|
Slug: "delta-team-slug",
|
|
Org: "gamma",
|
|
},
|
|
{
|
|
Name: "team3-name",
|
|
Slug: "team3-slug",
|
|
Org: "gamma",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "includes parent team in allowed orgs if present",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team-name-with-parent"),
|
|
Slug: github.Ptr("team-slug-with-parent"),
|
|
Parent: &github.Team{
|
|
Name: github.Ptr("parent-team-name"),
|
|
Slug: github.Ptr("parent-team-slug"),
|
|
Organization: nil, // the real GitHub API does not return Org on "Parent" team.
|
|
},
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("org-with-nested-teams"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team-name-with-same-parent-again"),
|
|
Slug: github.Ptr("team-slug-with-same-parent-again"),
|
|
Parent: &github.Team{
|
|
Name: github.Ptr("parent-team-name"),
|
|
Slug: github.Ptr("parent-team-slug"),
|
|
Organization: nil, // the real GitHub API does not return Org on "Parent" team.
|
|
},
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("org-with-nested-teams"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("parent-team-name"),
|
|
Slug: github.Ptr("parent-team-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("org-with-nested-teams"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team-name-with-parent-from-disallowed-org"),
|
|
Slug: github.Ptr("team-slug-with-parent-from-disallowed-org"),
|
|
Parent: &github.Team{
|
|
Name: github.Ptr("parent-team-name-from-disallowed-org"),
|
|
Slug: github.Ptr("parent-team-slug-from-disallowed-org"),
|
|
Organization: nil, // the real GitHub API does not return Org on "Parent" team.
|
|
},
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("disallowed-org"),
|
|
},
|
|
},
|
|
{
|
|
Name: github.Ptr("team-name-without-parent"),
|
|
Slug: github.Ptr("team-slug-without-parent"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("beta"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
allowedOrganizations: setutil.NewCaseInsensitiveSet("org-with-nested-teams", "beta"),
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "team-name-without-parent",
|
|
Slug: "team-slug-without-parent",
|
|
Org: "beta",
|
|
},
|
|
{
|
|
Name: "parent-team-name",
|
|
Slug: "parent-team-slug",
|
|
Org: "org-with-nested-teams",
|
|
},
|
|
{
|
|
Name: "team-name-with-parent",
|
|
Slug: "team-slug-with-parent",
|
|
Org: "org-with-nested-teams",
|
|
},
|
|
{
|
|
Name: "team-name-with-same-parent-again",
|
|
Slug: "team-slug-with-same-parent-again",
|
|
Org: "org-with-nested-teams",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "happy path with pagination",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchPages(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("page1-team-name"),
|
|
Slug: github.Ptr("page1-team-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("page1-org-name"),
|
|
},
|
|
},
|
|
},
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("page2-team-name"),
|
|
Slug: github.Ptr("page2-team-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("page2-org-name"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
token: "some-token",
|
|
allowedOrganizations: setutil.NewCaseInsensitiveSet("page1-org-name", "page2-org-name"),
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "page1-team-name",
|
|
Slug: "page1-team-slug",
|
|
Org: "page1-org-name",
|
|
},
|
|
{
|
|
Name: "page2-team-name",
|
|
Slug: "page2-team-slug",
|
|
Org: "page2-org-name",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "missing organization attribute returns an error",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team-name"),
|
|
Slug: github.Ptr("team-slug"),
|
|
},
|
|
},
|
|
),
|
|
),
|
|
wantErr: `error fetching team membership for authenticated user: missing the "organization" attribute for a team`,
|
|
},
|
|
{
|
|
name: "missing organization's login attribute returns an error",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team-name"),
|
|
Slug: github.Ptr("team-slug"),
|
|
Organization: &github.Organization{},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
wantErr: `error fetching team membership for authenticated user: missing the organization's "login" attribute for a team`,
|
|
},
|
|
{
|
|
name: "missing the name attribute for a team returns an error",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Slug: github.Ptr("team-slug"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("some-org"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
wantErr: `error fetching team membership for authenticated user: the "name" attribute is missing for a team`,
|
|
},
|
|
{
|
|
name: "missing the slug attribute for a team returns an error",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatch(
|
|
mock.GetUserTeams,
|
|
[]github.Team{
|
|
{
|
|
Name: github.Ptr("team-name"),
|
|
Organization: &github.Organization{
|
|
Login: github.Ptr("some-org"),
|
|
},
|
|
},
|
|
},
|
|
),
|
|
),
|
|
wantErr: `error fetching team membership for authenticated user: the "slug" attribute is missing for a team`,
|
|
},
|
|
{
|
|
name: "the token is added in the Authorization header",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUserTeams,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Len(t, r.Header["Authorization"], 1)
|
|
require.Equal(t, "Bearer does-this-token-work", r.Header.Get("Authorization"))
|
|
_, err := w.Write([]byte(`[{"name":"team1-name","slug":"team1-slug","organization":{"login":"org-login"}}]`))
|
|
require.NoError(t, err)
|
|
}),
|
|
),
|
|
),
|
|
token: "does-this-token-work",
|
|
allowedOrganizations: setutil.NewCaseInsensitiveSet("org-login"),
|
|
wantTeams: []TeamInfo{
|
|
{
|
|
Name: "team1-name",
|
|
Slug: "team1-slug",
|
|
Org: "org-login",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "passes the context parameter into the API call",
|
|
token: "some-token",
|
|
httpClient: mock.NewMockedHTTPClient(),
|
|
ctx: func() context.Context {
|
|
canceledCtx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
return canceledCtx
|
|
}(),
|
|
wantErr: "error fetching team membership for authenticated user: context canceled",
|
|
},
|
|
{
|
|
name: "returns errors from the API",
|
|
httpClient: mock.NewMockedHTTPClient(
|
|
mock.WithRequestMatchHandler(
|
|
mock.GetUserTeams,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mock.WriteError(
|
|
w,
|
|
http.StatusFailedDependency,
|
|
"some random client error",
|
|
)
|
|
}),
|
|
),
|
|
),
|
|
token: "some-token",
|
|
wantErr: "error fetching team membership for authenticated user: GET {SERVER_URL}/user/teams?per_page=100: 424 some random client error []",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
githubClient := &githubClient{
|
|
client: github.NewClient(test.httpClient).WithAuthToken(test.token),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if test.ctx != nil {
|
|
ctx = test.ctx
|
|
}
|
|
|
|
actual, err := githubClient.GetTeamMembership(ctx, test.allowedOrganizations)
|
|
if test.wantErr != "" {
|
|
rt, ok := test.httpClient.Transport.(*mock.EnforceHostRoundTripper)
|
|
require.True(t, ok)
|
|
test.wantErr = strings.ReplaceAll(test.wantErr, "{SERVER_URL}", rt.Host)
|
|
require.EqualError(t, err, test.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, actual)
|
|
require.Equal(t, test.wantTeams, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|