Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d4aece9

Browse files
committed
test: start implementing sync tests
1 parent 18ab14b commit d4aece9

File tree

5 files changed

+247
-3
lines changed

5 files changed

+247
-3
lines changed

coderd/idpsync/idpsync.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"strings"
77

8+
"github.com/golang-jwt/jwt/v4"
89
"github.com/google/uuid"
910
"golang.org/x/xerrors"
1011

@@ -29,7 +30,7 @@ var NewSync = func(logger slog.Logger, entitlements *entitlements.Set, settings
2930
type IDPSync interface {
3031
// ParseOrganizationClaims takes claims from an OIDC provider, and returns the
3132
// organization sync params for assigning users into organizations.
32-
ParseOrganizationClaims(ctx context.Context, _ map[string]interface{}) (OrganizationParams, *HttpError)
33+
ParseOrganizationClaims(ctx context.Context, _ jwt.MapClaims) (OrganizationParams, *HttpError)
3334
// SyncOrganizations assigns and removed users from organizations based on the
3435
// provided params.
3536
SyncOrganizations(ctx context.Context, tx database.Store, user database.User, params OrganizationParams) error

coderd/idpsync/organization.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66

7+
"github.com/golang-jwt/jwt/v4"
78
"github.com/google/uuid"
89
"golang.org/x/xerrors"
910

@@ -15,7 +16,7 @@ import (
1516
"github.com/coder/coder/v2/coderd/util/slice"
1617
)
1718

18-
func (s AGPLIDPSync) ParseOrganizationClaims(ctx context.Context, _ map[string]interface{}) (OrganizationParams, *HttpError) {
19+
func (s AGPLIDPSync) ParseOrganizationClaims(ctx context.Context, _ jwt.MapClaims) (OrganizationParams, *HttpError) {
1920
// nolint:gocritic // all syncing is done as a system user
2021
ctx = dbauthz.AsSystemRestricted(ctx)
2122

coderd/idpsync/organizations_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package idpsync
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golang-jwt/jwt/v4"
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/require"
9+
10+
"cdr.dev/slog/sloggers/slogtest"
11+
"github.com/coder/coder/v2/coderd/entitlements"
12+
"github.com/coder/coder/v2/testutil"
13+
)
14+
15+
func TestParseOrganizationClaims(t *testing.T) {
16+
t.Parallel()
17+
18+
t.Run("SingleOrgDeployment", func(t *testing.T) {
19+
t.Parallel()
20+
21+
s := NewAGPLSync(slogtest.Make(t, &slogtest.Options{}), entitlements.New(), SyncSettings{
22+
OrganizationField: "",
23+
OrganizationMapping: nil,
24+
OrganizationAssignDefault: true,
25+
})
26+
27+
ctx := testutil.Context(t, testutil.WaitMedium)
28+
29+
params, err := s.ParseOrganizationClaims(ctx, jwt.MapClaims{})
30+
require.Nil(t, err)
31+
32+
require.Empty(t, params.Organizations)
33+
require.True(t, params.IncludeDefault)
34+
require.False(t, params.SyncEnabled)
35+
})
36+
37+
t.Run("AGPL", func(t *testing.T) {
38+
t.Parallel()
39+
40+
// AGPL has limited behavior
41+
s := NewAGPLSync(slogtest.Make(t, &slogtest.Options{}), entitlements.New(), SyncSettings{
42+
OrganizationField: "orgs",
43+
OrganizationMapping: map[string][]uuid.UUID{
44+
"random": {uuid.New()},
45+
},
46+
OrganizationAssignDefault: false,
47+
})
48+
49+
ctx := testutil.Context(t, testutil.WaitMedium)
50+
51+
params, err := s.ParseOrganizationClaims(ctx, jwt.MapClaims{})
52+
require.Nil(t, err)
53+
54+
require.Empty(t, params.Organizations)
55+
require.False(t, params.IncludeDefault)
56+
require.False(t, params.SyncEnabled)
57+
})
58+
}

enterprise/coderd/enidpsync/organizations.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66

7+
"github.com/golang-jwt/jwt/v4"
78
"github.com/google/uuid"
89

910
"cdr.dev/slog"
@@ -12,7 +13,7 @@ import (
1213
"github.com/coder/coder/v2/codersdk"
1314
)
1415

15-
func (e EnterpriseIDPSync) ParseOrganizationClaims(ctx context.Context, mergedClaims map[string]interface{}) (idpsync.OrganizationParams, *idpsync.HttpError) {
16+
func (e EnterpriseIDPSync) ParseOrganizationClaims(ctx context.Context, mergedClaims jwt.MapClaims) (idpsync.OrganizationParams, *idpsync.HttpError) {
1617
if !e.entitlements.Enabled(codersdk.FeatureMultipleOrganizations) {
1718
// Default to agpl if multi-org is not enabled
1819
return e.AGPLIDPSync.ParseOrganizationClaims(ctx, mergedClaims)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package enidpsync
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/golang-jwt/jwt/v4"
8+
"github.com/google/uuid"
9+
"github.com/prometheus/client_golang/prometheus"
10+
"github.com/stretchr/testify/require"
11+
12+
"cdr.dev/slog/sloggers/slogtest"
13+
"github.com/coder/coder/v2/coderd/coderdtest"
14+
"github.com/coder/coder/v2/coderd/database"
15+
"github.com/coder/coder/v2/coderd/database/db2sdk"
16+
"github.com/coder/coder/v2/coderd/database/dbauthz"
17+
"github.com/coder/coder/v2/coderd/database/dbgen"
18+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
19+
"github.com/coder/coder/v2/coderd/entitlements"
20+
"github.com/coder/coder/v2/coderd/idpsync"
21+
"github.com/coder/coder/v2/coderd/rbac"
22+
"github.com/coder/coder/v2/codersdk"
23+
"github.com/coder/coder/v2/testutil"
24+
)
25+
26+
type ExpectedUser struct {
27+
SyncError bool
28+
Organizations []uuid.UUID
29+
}
30+
31+
type Expectations struct {
32+
Name string
33+
Claims jwt.MapClaims
34+
// Parse
35+
ParseError func(t *testing.T, httpErr *idpsync.HttpError)
36+
ExpectedParams idpsync.OrganizationParams
37+
// Mutate allows mutating the user before syncing
38+
Mutate func(t *testing.T, db database.Store, user database.User)
39+
Sync ExpectedUser
40+
}
41+
42+
type OrganizationSyncTestCase struct {
43+
Settings idpsync.SyncSettings
44+
Entitlements *entitlements.Set
45+
Exps []Expectations
46+
}
47+
48+
func TestOrganizationSync(t *testing.T) {
49+
t.Parallel()
50+
51+
if dbtestutil.WillUsePostgres() {
52+
t.Skip("Skipping test because it populates a lot of db entries, which is slow on postgres")
53+
}
54+
55+
requireUserOrgs := func(t *testing.T, db database.Store, user database.User, expected []uuid.UUID) {
56+
t.Helper()
57+
58+
// nolint:gocritic // in testing
59+
members, err := db.OrganizationMembers(dbauthz.AsSystemRestricted(context.Background()), database.OrganizationMembersParams{
60+
UserID: user.ID,
61+
})
62+
require.NoError(t, err)
63+
64+
foundIDs := db2sdk.List(members, func(m database.OrganizationMembersRow) uuid.UUID {
65+
return m.OrganizationMember.OrganizationID
66+
})
67+
require.ElementsMatch(t, expected, foundIDs, "match user organizations")
68+
}
69+
70+
entitled := entitlements.New()
71+
entitled.Update(func(entitlements *codersdk.Entitlements) {
72+
entitlements.Features[codersdk.FeatureMultipleOrganizations] = codersdk.Feature{
73+
Entitlement: codersdk.EntitlementEntitled,
74+
Enabled: true,
75+
Limit: nil,
76+
Actual: nil,
77+
}
78+
})
79+
80+
testCases := []struct {
81+
Name string
82+
Case func(t *testing.T, db database.Store) OrganizationSyncTestCase
83+
}{
84+
{
85+
Name: "SingleOrgDeployment",
86+
Case: func(t *testing.T, db database.Store) OrganizationSyncTestCase {
87+
def, _ := db.GetDefaultOrganization(context.Background())
88+
other := dbgen.Organization(t, db, database.Organization{})
89+
return OrganizationSyncTestCase{
90+
Entitlements: entitled,
91+
Settings: idpsync.SyncSettings{
92+
OrganizationField: "",
93+
OrganizationMapping: nil,
94+
OrganizationAssignDefault: true,
95+
},
96+
Exps: []Expectations{
97+
{
98+
Name: "NoOrganizations",
99+
Claims: jwt.MapClaims{},
100+
ExpectedParams: idpsync.OrganizationParams{
101+
SyncEnabled: false,
102+
IncludeDefault: true,
103+
Organizations: []uuid.UUID{},
104+
},
105+
Sync: ExpectedUser{
106+
Organizations: []uuid.UUID{},
107+
},
108+
},
109+
{
110+
Name: "AlreadyInOrgs",
111+
Claims: jwt.MapClaims{},
112+
ExpectedParams: idpsync.OrganizationParams{
113+
SyncEnabled: false,
114+
IncludeDefault: true,
115+
Organizations: []uuid.UUID{},
116+
},
117+
Mutate: func(t *testing.T, db database.Store, user database.User) {
118+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
119+
UserID: user.ID,
120+
OrganizationID: def.ID,
121+
})
122+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
123+
UserID: user.ID,
124+
OrganizationID: other.ID,
125+
})
126+
},
127+
Sync: ExpectedUser{
128+
Organizations: []uuid.UUID{def.ID, other.ID},
129+
},
130+
},
131+
},
132+
}
133+
},
134+
},
135+
}
136+
137+
for _, tc := range testCases {
138+
tc := tc
139+
t.Run(tc.Name, func(t *testing.T) {
140+
t.Parallel()
141+
ctx := testutil.Context(t, testutil.WaitMedium)
142+
logger := slogtest.Make(t, &slogtest.Options{})
143+
144+
rdb, _ := dbtestutil.NewDB(t)
145+
db := dbauthz.New(rdb, rbac.NewAuthorizer(prometheus.NewRegistry()), logger, coderdtest.AccessControlStorePointer())
146+
caseData := tc.Case(t, rdb)
147+
if caseData.Entitlements == nil {
148+
caseData.Entitlements = entitlements.New()
149+
}
150+
151+
// Create a new sync object
152+
sync := NewSync(logger, caseData.Entitlements, caseData.Settings)
153+
for _, exp := range caseData.Exps {
154+
t.Run(exp.Name, func(t *testing.T) {
155+
params, httpErr := sync.ParseOrganizationClaims(ctx, exp.Claims)
156+
if exp.ParseError != nil {
157+
exp.ParseError(t, httpErr)
158+
return
159+
}
160+
161+
require.Equal(t, exp.ExpectedParams.SyncEnabled, params.SyncEnabled, "match enabled")
162+
require.Equal(t, exp.ExpectedParams.IncludeDefault, params.IncludeDefault, "match include default")
163+
if exp.ExpectedParams.Organizations == nil {
164+
exp.ExpectedParams.Organizations = []uuid.UUID{}
165+
}
166+
require.ElementsMatch(t, exp.ExpectedParams.Organizations, params.Organizations, "match organizations")
167+
168+
user := dbgen.User(t, db, database.User{})
169+
if exp.Mutate != nil {
170+
exp.Mutate(t, db, user)
171+
}
172+
173+
err := sync.SyncOrganizations(ctx, db, user, params)
174+
if exp.Sync.SyncError {
175+
require.Error(t, err)
176+
return
177+
}
178+
requireUserOrgs(t, db, user, exp.Sync.Organizations)
179+
})
180+
}
181+
})
182+
}
183+
}

0 commit comments

Comments
 (0)