Skip to content

Commit bcd23a1

Browse files
committed
test: start implementing sync tests
1 parent 16eb96e commit bcd23a1

File tree

5 files changed

+247
-3
lines changed

5 files changed

+247
-3
lines changed

coderd/idpsync/idpsync.go

Lines changed: 2 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 58 additions & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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)
Lines changed: 183 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy