Skip to content

Commit 1f5f69a

Browse files
committed
add tests
1 parent ef322d4 commit 1f5f69a

File tree

7 files changed

+66
-17
lines changed

7 files changed

+66
-17
lines changed

coderd/audit/audit.go

+15
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,18 @@ func (nop) Export(context.Context, database.AuditLog) error {
2222
}
2323

2424
func (nop) diff(any, any) Map { return Map{} }
25+
26+
func NewMock() *MockAuditor {
27+
return &MockAuditor{}
28+
}
29+
30+
type MockAuditor struct {
31+
AuditLogs []database.AuditLog
32+
}
33+
34+
func (a *MockAuditor) Export(_ context.Context, alog database.AuditLog) error {
35+
a.AuditLogs = append(a.AuditLogs, alog)
36+
return nil
37+
}
38+
39+
func (*MockAuditor) diff(any, any) Map { return Map{} }

coderd/audit/request.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
115115
ctx := context.Background()
116116
logCtx := p.Request.Context()
117117

118+
// If no resources were provided, there's nothing we can audit.
118119
if ResourceID(req.Old) == uuid.Nil && ResourceID(req.New) == uuid.Nil {
119-
p.Log.Error(logCtx, "both old and new are nil, cannot audit")
120120
return
121121
}
122122

coderd/coderd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func New(options *Options) *API {
114114
options.LicenseHandler = licenses()
115115
}
116116
if options.FeaturesService == nil {
117-
options.FeaturesService = featuresService{}
117+
options.FeaturesService = &featuresService{}
118118
}
119119

120120
siteCacheDir := options.CacheDir

coderd/coderdtest/coderdtest.go

+8
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"cdr.dev/slog"
4444
"cdr.dev/slog/sloggers/slogtest"
4545
"github.com/coder/coder/coderd"
46+
"github.com/coder/coder/coderd/audit"
4647
"github.com/coder/coder/coderd/autobuild/executor"
4748
"github.com/coder/coder/coderd/awsidentity"
4849
"github.com/coder/coder/coderd/database"
@@ -74,6 +75,7 @@ type Options struct {
7475
AutoImportTemplates []coderd.AutoImportTemplate
7576
AutobuildTicker <-chan time.Time
7677
AutobuildStats chan<- executor.Stats
78+
Auditor audit.Auditor
7779

7880
// IncludeProvisionerDaemon when true means to start an in-memory provisionerD
7981
IncludeProvisionerDaemon bool
@@ -197,6 +199,11 @@ func newWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c
197199
_ = turnServer.Close()
198200
})
199201

202+
features := coderd.DisabledImplementations
203+
if options.Auditor != nil {
204+
features.Auditor = options.Auditor
205+
}
206+
200207
// We set the handler after server creation for the access URL.
201208
coderAPI := options.APIBuilder(&coderd.Options{
202209
AgentConnectionUpdateFrequency: 150 * time.Millisecond,
@@ -240,6 +247,7 @@ func newWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c
240247
AutoImportTemplates: options.AutoImportTemplates,
241248
MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval,
242249
AgentStatsRefreshInterval: options.AgentStatsRefreshInterval,
250+
FeaturesService: coderd.NewMockFeaturesService(features),
243251
})
244252
t.Cleanup(func() {
245253
_ = coderAPI.Close()

coderd/features.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,31 @@ import (
77
"golang.org/x/xerrors"
88

99
"github.com/coder/coder/coderd/audit"
10+
"github.com/coder/coder/coderd/features"
1011
"github.com/coder/coder/coderd/httpapi"
1112
"github.com/coder/coder/codersdk"
1213
)
1314

14-
type featuresService struct{}
15+
func NewMockFeaturesService(feats FeatureInterfaces) features.Service {
16+
return &featuresService{
17+
feats: &feats,
18+
}
19+
}
20+
21+
type featuresService struct {
22+
feats *FeatureInterfaces
23+
}
1524

16-
func (featuresService) EntitlementsAPI(rw http.ResponseWriter, _ *http.Request) {
17-
features := make(map[string]codersdk.Feature)
25+
func (*featuresService) EntitlementsAPI(rw http.ResponseWriter, _ *http.Request) {
26+
feats := make(map[string]codersdk.Feature)
1827
for _, f := range codersdk.FeatureNames {
19-
features[f] = codersdk.Feature{
28+
feats[f] = codersdk.Feature{
2029
Entitlement: codersdk.EntitlementNotEntitled,
2130
Enabled: false,
2231
}
2332
}
2433
httpapi.Write(rw, http.StatusOK, codersdk.Entitlements{
25-
Features: features,
34+
Features: feats,
2635
Warnings: []string{},
2736
HasLicense: false,
2837
})
@@ -32,7 +41,7 @@ func (featuresService) EntitlementsAPI(rw http.ResponseWriter, _ *http.Request)
3241
// struct type containing feature interfaces as fields. The AGPL featureService always returns the
3342
// "disabled" version of the feature interface because it doesn't include any enterprise features
3443
// by definition.
35-
func (featuresService) Get(ps any) error {
44+
func (f *featuresService) Get(ps any) error {
3645
if reflect.TypeOf(ps).Kind() != reflect.Pointer {
3746
return xerrors.New("input must be pointer to struct")
3847
}
@@ -46,7 +55,7 @@ func (featuresService) Get(ps any) error {
4655
if tf.Kind() != reflect.Interface {
4756
return xerrors.Errorf("fields of input struct must be interfaces: %s", tf.String())
4857
}
49-
err := setImplementation(vf, tf)
58+
err := f.setImplementation(vf, tf)
5059
if err != nil {
5160
return err
5261
}
@@ -56,11 +65,16 @@ func (featuresService) Get(ps any) error {
5665

5766
// setImplementation finds the correct implementation for the field's type, and sets it on the
5867
// struct. It returns an error if unsuccessful
59-
func setImplementation(vf reflect.Value, tf reflect.Type) error {
68+
func (f *featuresService) setImplementation(vf reflect.Value, tf reflect.Type) error {
69+
feats := f.feats
70+
if feats == nil {
71+
feats = &DisabledImplementations
72+
}
73+
6074
// when we get more than a few features it might make sense to have a data structure for finding
6175
// the correct implementation that's faster than just a linear search, but for now just spin
6276
// through the implementations we have.
63-
vd := reflect.ValueOf(DisabledImplementations)
77+
vd := reflect.ValueOf(*feats)
6478
for j := 0; j < vd.NumField(); j++ {
6579
vdf := vd.Field(j)
6680
if vdf.Type() == tf {

coderd/features_internal_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestEntitlements(t *testing.T) {
1919
t.Parallel()
2020
r := httptest.NewRequest("GET", "https://example.com/api/v2/entitlements", nil)
2121
rw := httptest.NewRecorder()
22-
featuresService{}.EntitlementsAPI(rw, r)
22+
(&featuresService{}).EntitlementsAPI(rw, r)
2323
resp := rw.Result()
2424
defer resp.Body.Close()
2525
assert.Equal(t, http.StatusOK, resp.StatusCode)

coderd/users_test.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010
"time"
1111

1212
"github.com/google/uuid"
13+
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/require"
1415
"golang.org/x/sync/errgroup"
1516

1617
"github.com/coder/coder/coderd"
18+
"github.com/coder/coder/coderd/audit"
1719
"github.com/coder/coder/coderd/coderdtest"
1820
"github.com/coder/coder/coderd/rbac"
1921
"github.com/coder/coder/codersdk"
@@ -374,7 +376,8 @@ func TestPostUsers(t *testing.T) {
374376

375377
t.Run("Create", func(t *testing.T) {
376378
t.Parallel()
377-
client := coderdtest.New(t, nil)
379+
auditor := audit.NewMock()
380+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
378381
user := coderdtest.CreateFirstUser(t, client)
379382

380383
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
@@ -387,6 +390,7 @@ func TestPostUsers(t *testing.T) {
387390
Password: "testing",
388391
})
389392
require.NoError(t, err)
393+
assert.Len(t, auditor.AuditLogs, 1)
390394
})
391395
}
392396

@@ -435,7 +439,8 @@ func TestUpdateUserProfile(t *testing.T) {
435439

436440
t.Run("UpdateUsername", func(t *testing.T) {
437441
t.Parallel()
438-
client := coderdtest.New(t, nil)
442+
auditor := audit.NewMock()
443+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
439444
coderdtest.CreateFirstUser(t, client)
440445

441446
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
@@ -447,6 +452,7 @@ func TestUpdateUserProfile(t *testing.T) {
447452
})
448453
require.NoError(t, err)
449454
require.Equal(t, userProfile.Username, "newusername")
455+
assert.Len(t, auditor.AuditLogs, 1)
450456
})
451457
}
452458

@@ -496,7 +502,8 @@ func TestUpdateUserPassword(t *testing.T) {
496502
})
497503
t.Run("MemberCanUpdateOwnPassword", func(t *testing.T) {
498504
t.Parallel()
499-
client := coderdtest.New(t, nil)
505+
auditor := audit.NewMock()
506+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
500507
admin := coderdtest.CreateFirstUser(t, client)
501508
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
502509

@@ -508,6 +515,7 @@ func TestUpdateUserPassword(t *testing.T) {
508515
Password: "newpassword",
509516
})
510517
require.NoError(t, err, "member should be able to update own password")
518+
assert.Len(t, auditor.AuditLogs, 2)
511519
})
512520
t.Run("MemberCantUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
513521
t.Parallel()
@@ -525,7 +533,8 @@ func TestUpdateUserPassword(t *testing.T) {
525533
})
526534
t.Run("AdminCanUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
527535
t.Parallel()
528-
client := coderdtest.New(t, nil)
536+
auditor := audit.NewMock()
537+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
529538
_ = coderdtest.CreateFirstUser(t, client)
530539

531540
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
@@ -535,6 +544,7 @@ func TestUpdateUserPassword(t *testing.T) {
535544
Password: "newpassword",
536545
})
537546
require.NoError(t, err, "admin should be able to update own password without providing old password")
547+
assert.Len(t, auditor.AuditLogs, 1)
538548
})
539549
}
540550

@@ -752,7 +762,8 @@ func TestPutUserSuspend(t *testing.T) {
752762

753763
t.Run("SuspendAnotherUser", func(t *testing.T) {
754764
t.Parallel()
755-
client := coderdtest.New(t, nil)
765+
auditor := audit.NewMock()
766+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
756767
me := coderdtest.CreateFirstUser(t, client)
757768
_, user := coderdtest.CreateAnotherUserWithUser(t, client, me.OrganizationID)
758769

@@ -762,6 +773,7 @@ func TestPutUserSuspend(t *testing.T) {
762773
user, err := client.UpdateUserStatus(ctx, user.Username, codersdk.UserStatusSuspended)
763774
require.NoError(t, err)
764775
require.Equal(t, user.Status, codersdk.UserStatusSuspended)
776+
assert.Len(t, auditor.AuditLogs, 2)
765777
})
766778

767779
t.Run("SuspendItSelf", func(t *testing.T) {

0 commit comments

Comments
 (0)